What is the Adapter Pattern?
The Adapter Pattern is a structural design pattern that allows objects with incompatible interfaces to work together. It acts like a bridge between two different interfaces.
In simple terms:
“Convert the interface of a class into another interface that clients expect.”
When to Use the Adapter Pattern?
- When you want to use an existing class but its interface doesn’t match your needs.
- When integrating legacy code or third-party libraries into your codebase.
- When building cross-platform bridges in Flutter (e.g., iOS/Android native APIs).
Real-World Example in Dart: Audio Player Adapter
Scenario:
You have a legacy audio player class with a method playFile(String path),
but your app expects a MediaPlayer interface with play(String filename).
Step 1: Target Interface (What your app expects)
abstract class MediaPlayer {
void play(String filename);
}
Step 2: Existing Incompatible Class (Legacy or 3rd-party)
class LegacyAudioPlayer {
void playFile(String path) {
print("Playing audio file: $path using LegacyAudioPlayer");
}
}
Step 3: Create the Adapter
class AudioPlayerAdapter implements MediaPlayer {
final LegacyAudioPlayer legacyPlayer;
AudioPlayerAdapter(this.legacyPlayer);
@override
void play(String filename) {
// Adapt the method call
legacyPlayer.playFile(filename);
}
}
Step 4: Use It in Client Code
void main() {
// Original incompatible class
LegacyAudioPlayer legacy = LegacyAudioPlayer();
// Adapter wraps the legacy class
MediaPlayer player = AudioPlayerAdapter(legacy);
// Client code uses the expected interface
player.play("song.mp3");
}
Output:
Playing audio file: song.mp3 using LegacyAudioPlayer
Explanation
- The Adapter (
AudioPlayerAdapter) implements the expected interface (MediaPlayer). - It internally uses the incompatible class (
LegacyAudioPlayer) and translates the call. - The client stays decoupled from the implementation and works with a unified interface.
Benefits of Adapter Pattern
- Enables code reuse by adapting incompatible classes.
- Promotes loose coupling between client and service implementations.
- Helps with migration from legacy systems.
- Makes it easy to mock or replace dependencies for testing.
Drawbacks
- Adds extra layers of abstraction.
- Can become messy if overused or combined with multiple adapters.
- May hide complex transformations that reduce performance clarity.
Flutter Use Cases
- Creating platform bridges with
MethodChannel(Android/iOS → Dart). - Wrapping native plugins in Flutter with a unified interface.
- Adapting APIs from different backend services to your app’s models.
Summary Table
| Attribute | Adapter Pattern |
|---|---|
| Purpose | Bridge incompatible interfaces |
| Common Use Cases | API wrapping, legacy integration |
| Easy in Dart? | Yes, via abstract classes + composition |
| Useful in Flutter? | Especially with plugins/platforms |



