Adapter Design Pattern In Flutter

The adapter design pattern, also known as Wrapper, is a popular structural design pattern in software development. If you're unsure what a design pattern is, have a look at my earlier articles on the design pattern series. In this article, we'll cover.

  1. What is the Adapter Design Pattern?
  2. An Example of an Adapter Design Pattern?
  3. When to use?
  4. Pitfalls
  5. Conclusion

Let's get started without any further delay.

What is the Adapter Design Pattern?

According to the book "Design Patterns, Elements of Reusable Object-Oriented Software," the Adapter design pattern converts the interface of a class into another interface clients expect. The adapter lets classes work together that couldn't otherwise because of incompatible interfaces.

Let’s try to understand with a real-world analogy. Suppose you have a music player in your car that only supports cassettes, but you want to listen to music from your smartphone. Here, you would use a cassette adapter. The adapter has an interface compatible with the cassette player and provides an interface for your smartphone to use.

So we can say adapter design patterns are just like a real-life adapter which to used for different purposes.

An Example Of an Adapter Design Pattern

Now, let’s try to understand with a coding sample. Imagine you're working with two data sources: UserAPI, which provides data in JSON format, and EmployeeAPI, which provides data in XML format. However, your application requires data in JSON format. In this scenario, the Adapter Design Pattern comes into play. You can create an adapter, XMLToJSONAdapter, that takes the XML data from EmployeeAPI and converts it into JSON format. This allows your application to handle data from both APIs in a consistent manner, despite the original data formats being different.

Design Pattern

import 'dart:convert';

// This is the "Target" class
abstract class JSONDataInterface {
  String getData();
}

// Adaptee 1
class UserAPI implements JSONDataInterface {
  @override
  String getData() {
    // Simulate JSON data from API
    return jsonEncode({"name": "John", "age": 30});
  }
}

// Adaptee 2
class EmployeeAPI {
  String getData() {
    // Simulate XML data from API
    return "<employee><name>Alice</name><age>25</age></employee>";
  }
}

// The Adapter
class XMLToJSONAdapter implements JSONDataInterface {
  EmployeeAPI _employeeApi;
  XMLToJSONAdapter(this._employeeApi);

  @override
  String getData() {
    // Convert XML to JSON
    var xmlData = _employeeApi.getData();
    var name = xmlData.split("<name>")[1].split("</name>")[0];
    var age = xmlData.split("<age>")[1].split("</age>")[0];
    var jsonData = jsonEncode({"name": name, "age": age});
    return jsonData;
  }
}

// Usage
void displayData(JSONDataInterface api) {
  print(api.getData());
}

void main() {
  var userApi = UserAPI();
  var employeeApi = EmployeeAPI();
  var adapter = XMLToJSONAdapter(employeeApi);

  displayData(userApi);  // Outputs: {"name":"John","age":30}
  displayData(adapter);  // Outputs: {"name":"Alice","age":"25"}
}

UserAPI returns JSON data directly, while EmployeeAPI returns XML data. The XMLToJSONAdapter class is used to convert the XML data from EmployeeAPI into JSON format, making it compatible with the rest of the system that expects JSON data.

Note. The Adapter design pattern follows the Single Responsibility Principle (SRP) and Open-Closed Principle (OCP). If you are unfamiliar with these concepts, please check out my article on SOLID Principles in Flutter Development.

When To Use?

  • When you need to use an existing class that has an interface different from what you require.
  • When you want to create a reusable class that cooperates with unrelated or unforeseen classes, i.e., classes that don't necessarily have compatible interfaces.
  • When it is impractical to adapt the interface of several existing subclasses by subclassing each one, you can use an adapter to adapt the interface of its parent class.

Pitfalls

  • The Adapter Pattern can make the program structure more complex by introducing many small and similar classes, making the code harder to understand at first glance.
  • The process of adapting data can be expensive in terms of performance, especially when dealing with large amounts of data or complex data structures.

Conclusion

The Adapter Design Pattern is useful for ensuring compatibility between classes with incompatible interfaces. It provides greater flexibility and reusability of existing code, making it simpler to integrate new classes with old code. However, like any design pattern, it needs to be used carefully, considering the possible pitfalls and the complexity it can add to the codebase. If you find my articles useful, do connect with me on LinkedIn to share your thoughts that motivate me.

References


Similar Articles