When you start working with Flutter, it's normal to feel that the widgets live in their own world. Each one is tucked into its branch of the tree, and communicating them with each other can become a headache if you don't use a good reactive scheme. That's exactly where the Observer Pattern shines: it allows you to automatically react to changes without writing repeated code or coupling components together.
In my case, the first time I used Observer in Flutter was to update the "available balance" on screen without having to move half the widget tree. I had to change a value and wanted everything dependent on it to refresh without me doing anything extra. And that's when I said: "ok... Observer really makes sense."
This pattern allows related objects to stay in sync and automatically react to changes in the observed object, which is excellent in Flutter thanks to its reactive component; this facilitates coordination and communication between the components of a system.
Remember that the pattern we presented earlier allows us to create a single instance, the Singleton in Flutter.
If I had to explain to someone how to start with reactivity in Flutter today, I'd start here.
What is the Observer pattern and why is it important in Flutter?
Simple explanation of the Observer pattern
The Observer pattern establishes a one-to-many relationship:
- There is a main object (Observable or Subject).
- There is one or many Observers.
When the Observable changes, it notifies all the Observers. They react, update themselves, or change their state as needed.
What I like about this approach is that it doesn't matter if you have 1 observer or 1000, they all listen to the same thing, they all react the same way, and you don't worry about who is who.
One-to-Many Relationship and Automatic Notifications
The Observable doesn't know who is listening, it just knows it has a list. The communication is a broadcast. It notifies, and that's it.
How it fits with Flutter's reactive model
Flutter is a reactive framework. Widgets rebuild when there are state changes. The Observer pattern integrates perfectly because the idea is precisely that:
“I changed a piece of data → notify it → those who depend on the data update themselves”.
When I understood the reactive nature of Flutter, I started using Observer to test behaviors before diving into bigger tools like Provider, Bloc, or Riverpod.
The Observer pattern helps us subscribe to some events of a Class and receive notifications. This means there is someone who emits data and another who is going to listen to it. The one who emits the data is simply responsible for sending it to whoever is subscribed to that data and nothing more. The entities that are listening can be removed or added, and for the other entity, it is completely transparent.
Benefits
Abstract coupling between Subject and Observer. All a subject knows is that it has a list of observers, each of which conforms to the simple interface of the abstract Observer class. The subject knows nothing of the concrete class of any observer. Thus, the coupling between subjects and observers is abstract and minimal.
Support for broadcast communication. Unlike an ordinary request, the notification a subject sends doesn't need to specify its receiver. The notification is broadcast automatically to all interested objects that subscribed to it. The subject doesn't care how many interested objects exist; its sole responsibility is to notify its observers. This gives you the freedom to add and remove observers at any time. It is up to the observer to handle or ignore a notification.
Drawbacks
Unexpected updates. Because observers have no knowledge of each other's presence, they may be blind to the ultimate cost of subject change. A seemingly innocuous operation on the subject may cause a cascade of updates to the observers and their dependent objects.
When to use the Observer pattern?
- When several views depend on one piece of data, we can subscribe to the events of that data, and when it changes, we update all views reactively.
- When an abstraction has two aspects, one dependent on the other. Encapsulating these aspects in separate objects lets you vary and reuse them independently.
- When a change in one object requires changing others and you don't know how many objects need to change.
- When an object should be able to notify other objects without making assumptions about who these objects are. In other words, you don't want these objects to be tightly coupled.
- Several widgets need to listen to the same data.
- You work with simple models that change frequently (price, balance, progress, notifications).
- You want to practice software design without jumping directly into heavy libraries.
Signs you need Observer (and not another pattern)
- If you have multiple dependencies on the same state.
- If you want minimal coupling between components.
- If you want a native Dart solution without external libraries.
Limitations and Frequent Bad Practices
- Having too many observers can lead to cascades of updates.
- Poor subscription management → memory leaks.
- Using it for large projects without architecture → chaos.
Implementation of the Observer pattern in Dart
For our example, we will start by creating two interfaces or Abstract Classes in Dart, which will help us delegate communication between Classes. The first is the Observable and the other is the Observer.
We are going to build the classic pattern structure using your real example of `AvailableBalanceObservable`.
import 'package:design_patterns/design_patterns/observer/observable/observer.dart';
abstract class Observable {
void addObserver(Observer observer) {}
void removeObserver(Observer observer) {}
void notifyObservers(double newValue) {}
}In my first implementations, I liked to keep it that simple. Nothing weird, nothing complex.
Creating a real Subject: AvailableBalanceObservable
Now, your typical case: managing a user's available balance.
abstract class Observer {
void notifyChange(double newValue) {}
}Now we create an `AvailableBalanceObservable` Class that is responsible for handling available balance events. In this class, we implement the `Observable`. This forces us to implement the `addObserver`, `removeObserver`, and `notifyObservers` methods.
To register the observers, we must save them in memory, for this we create a `_amountObserverList` variable where we have the list of our Observers. The same is true for removing an observer.
When we are going to generate a change and we are going to notify everyone, what we do is notify each of the Observers we have.
Now we create the function to change the available balance. When we update the amount, we also notify the Observers.
import 'package:design_patterns/design_patterns/observer/observable/observable.dart';
import 'package:design_patterns/design_patterns/observer/observable/observer.dart';
class AvailableBalanceObservable implements Observable {
final List<Observer> _amountObserverList = [];
double _amount = 0.0;
void changeAmount(double newValue) {
_amount = newValue;
notifyObservers(_amount);
}
@override
void addObserver(Observer observer) {
_amountObserverList.add(observer);
}
@override
void removeObserver(Observer observer) {
_amountObserverList.remove(observer);
}
@override
void notifyObservers(double newValue) {
for (var observer in _amountObserverList) {
observer.notifyChange(newValue);
}
}
}Here it's very clear how it works: you update, you notify, and each observer does its thing. The general idea is to call `notifyChange` or keep adding/removing observers to apply the changes.
How Observers subscribe and get notified
An observer might look like this:
class BalanceWidgetObserver implements Observer {
@override
void notifyChange(double newValue) {
print("Nuevo saldo: $newValue");
}
}Complete Example with Value Update
void main() {
final balance = AvailableBalanceObservable();
final widgetObserver = BalanceWidgetObserver();
balance.addObserver(widgetObserver);
balance.changeAmount(150.50);
balance.changeAmount(200.00);
}So, it is divided into two sections: on the one hand, implementing the logic for the observable and then implementing the listeners or observers as we showed before.
Observer vs Other Alternatives in Flutter
Observer vs Provider
- Provider is optimized for widgets and specific reconstructions.
- Observer is lighter, raw, and flexible.
- For small projects: Observer.
- For medium/large apps: Provider or similar.
Observer vs Streams
- Streams are used for asynchronous data flows.
- Observer is synchronous, immediate, and more predictable.
- If you are going to receive data in real-time (chat, sockets), Streams give you more control.
Observer vs ChangeNotifier / ValueNotifier
- ChangeNotifier is essentially an evolution of the Observer adapted to the Flutter world.
- ValueNotifier is ideal for simple values.
- Observer is still useful for learning the underlying design.
Advantages and Disadvantages of the Observer Pattern
Benefits
- Minimal coupling.
- Immediate one-to-many communication.
- Ideal for clean and extensible code.
- Reduces repetitive logic.
Drawbacks
- Unexpected updates if you don't properly control who is listening.
- Can grow in complexity if you don't organize your observers.
- Not the best option for large applications.
Best Practices for Implementing Observer in Flutter
- Memory management and preventing leaks
- Always remove observers when the widget is destroyed. A well-used `dispose()` saves you trouble.
- Code organization for scaling
- Separate the Observable from the UI code. Keep your Subject in models or controllers.
- Examples of performance improvements
- Avoid nesting notifications.
- Make copies of the list before iterating when there are many subscriptions.
- Notify only when the value actually changed.
Frequently Asked Questions about the Observer Pattern in Flutter (FAQ)
- Is it a good option for large apps?
- Not for managing the entire state, but yes for isolated pieces that change constantly.
- Can I mix it with other state managers?
- Yes, without a problem. You can use Observer for specific cases and Provider/Bloc for the rest.
- When to avoid Observer?
- When your application needs a long-term maintainable architecture, preferably with modern tools.
Conclusion
The Observer pattern is still alive and well and makes perfect sense within Flutter, especially once you start to understand the reactive nature of the framework. For me, it was a key tool at the beginning: simple, clear, and perfect for understanding how changes are communicated within an app.
If I had to explain to someone today how to get started with reactivity in Flutter, I would begin here.
The next step is to learn how to use and inherit a Flutter StatefulWidget, which is very useful in the modularization of our app.
I agree to receive announcements of interest about this Blog.
The Observer pattern allows you to define entities that send events and another entity that subscribes to those events. Let's see how to use them in Flutter; we'll look at the concept, usage examples, and recommendations.