Inter-feature communication in Flutter apps

Georgi Stanev
Prime Holding JSC
Published in
3 min readJun 10, 2022

What does inter-feature communication in the context of mobile applications mean and why do we even need it? Let’s face it, users nowadays are more spoiled than ever. To satisfy their expectations, most modern apps need to provide highly interactive UX. This means that when the user executes an action on a particular page they expect the data they work with to be up-to-date all the time in the entire application. Let’s take a look at the famous Reminders App, where the user creates a reminder from the Reminder List Page, and the respective count in the Dashboard Page is refreshed automatically.

So now the question is how can we as developers implement this behavior most efficiently? If you are new to Flutter don’t be scared. This is easily achievable especially if you have the utilities you need in your state management solution. Let’s see how we can implement this behavior using the rx_bloc package.

Event-based approach

We have two pages, the Dashboard Page and the Reminder List Page. They are decoupled from each other but in order to implement the needed functionality, we need to establish the inter-feature communication channel, where our choice is the Coordinator Pattern

Whenever the user updates/deletes/create a reminder in the Reminder List Page an event is sent to the Coordinator, and all pages (such as the Dashboard) receive the reminder so they can update their own state (in our case the count of reminders). Sounds clean, doesn’t it? Let’s see some code.

Sending the created reminder to the Coordinator

The UI Layer

...
ElevatedButton(
child: Text('Submit'),
onPressed: () => context
.read<ReminderListBlocType>()
.events
.create(_titleEditingController.text)
)
...

From the UI Layer (a.k.a the widget tree) we simply call the `create` event as passing the title of the reminder.

The Business Layer

class ReminderListEvents {

/// Create a reminder with the provided [title]
void create(String title);
}
class ReminderListStates{

/// The state of the newly created reminder
Stream<ReminderModel> get onReminderCreated;
}
@RxBloc()
class ReminderListBloc extends $ReminderManageBloc {

final RemindersService _service;
final CoordinatorBloc _coordinatorBloc;


@override
Stream<ReminderModel> _mapToOnReminderCreatedState() =>
_$createEvent
.asyncMap((title) => _service.create(title: title))
.doOnData(_coordinatorBloc.events.reminderCreated);
...
}

Once the UI Layer sends the reminder’s title through the create(title) event, the subject of which is _$createEvent, we try to save the reminder in the API. In case it’s saved successfully the created reminder is sent to the CoordinatorBloc.

Receiving updates

The Business Layer

abstract class DashboardStates {
@RxIgnoreState()
Stream<DashboardModel> get dashboard;
}
@RxBloc()
class DashboardBloc extends $DashboardBloc {
DashboardBloc(CoordinatorBloc coordinatorBloc) {

coordinatorBloc.states
.reminderCreated
.map((reminder) => _copyDashboardWithReminder(reminder))
.bind(_dashboardModel)
.addTo(_compositeSubscription);
}
/// Copy the DashboardModel with calculated count
/// based on the passed [reminder] object.
DashboardModel _copyDashboardWithReminder(
ReminderModel reminder
) {
......
}
/// The internal state of the dashboard model
final _dashboardModel = BehaviourSubject<DashboardModel>()
@override
Stream<DashboardModel> get dashboard => _dashboardModel;
....
}

The Dashboard BloC listens for the reminder “created event” and when such is received the _copyDashboardWithReminder is being called. This increases the count based on the received object, then the whole dashboard model is updated and the UI layer updates accordingly so that the user can see the change.

The UI Layer

...
RxBlocBuilder<DashboardBlocType, DashboardModel>(
state: (bloc) => bloc.states.dashboard,
builder: (context, dashboardModel, bloc) => ...,
)
...

In the widget tree, we simply listen for any state change of the dashboard model and then build the appropriate UI representation.

Conclusion

In this article, you learned how to satisfy the user expectations of having up-to-date data in your application without any user interaction. We achieved this by building a simple, scalable, and easy-to-use inter-feature communication channel. In the following article, we will dive deep into more complex scenarios where a single reminder will be automatically refreshed on Page X when updated on Page Y.

--

--