- Published at
Decoupling Services in Riverpod using Event Providers

Decoupling Riverpod systems using event providers and object wrappers
- Authors
-
-
- Name
- Joachim Bülow
- Cofounder and CTO at Doubble
-
Table of Contents
Decoupling Services in Riverpod using Event Providers
One common challenge in Flutter applications using Riverpod is managing service coupling.
Today, I’ll share a simple pattern I use to decouple services using event providers
and object wrappers
.
The Problem
When building complex Flutter applications, services often become tightly coupled as requirements are squeezed in over time.
Usually this happens when we want to account for side effects - either one provider invalidates
other providers when they perform actions, or other providers need to watch
other providers’ state to react to the state change.
The Solution: Event Providers
One solution is to create event providers that act as a communication channel between services. Here’s how it works:
enum ChatEvent {
MessageSent,
}
/// Provider to handle chat events
@riverpod
class ChatEvents extends _$ChatEvents {
@override
ObjectWrapper<ChatEvent>? build() {
return null;
}
messageSent() {
state = ChatEvent.MessageSent.toObject;
}
}
/// Keeps simple data types always reactive when equal event types are emitted
class ObjectWrapper<T> {
final T value;
ObjectWrapper(this.value);
}
/// Provider which handles sending messages
@riverpod
class MessageProvider(ChatId chatId) {
...
Future<void> sendMessage(String mes) {
...
final res = await repository.send(mes, chatId).guard();
if (!res.hasError) {
ref.read(chatEventsProvider.notifier).messageSent();
}
...
}
...
}
Then in your UI layer, or simply in other providers, you can listen to these events:
... within a widget
ref.listen(chatEventsProvider, (prev, next) async {
if (!mounted || next == null) {
return;
}
switch (next.value) {
case ChatEvent.MessageSent:
// Scroll to top of screen to highlight sent message
scrollToTop();
break;
default:
// handle other chat UI events here
}
});
Why This Works
-
Decoupling: Services and widgets don’t need to know about each other. In our example, we just know a message was sent. This gives us semantic freedom as well - as we do not rely on specific implicit state changes to react correctly.
-
Reactivity: React to multiple of the same events being emitted by leveraging Riverpod’s object reference comparisons
Conclusion
This pattern is similar to how RxJS works in web development, but adapted for Flutter and Riverpod. It’s a simple yet powerful way to keep your services decoupled while maintaining type safety and reactivity.