Receiving result from modal routes in Flutter with app_state package

Alexey Inkin
Flutter Senior
Published in
4 min readApr 3, 2022

--

This is a part of the series on app_state package. The beginning is here.

In the previous part, we made an app with two tabs with independent navigation stacks in each tab. But we only had been passing data to newly opened pages. Let’s learn to do the reverse.

In pure Flutter, we open new screens with Navigator’s push method. It returns a Future which completes when that screen is closed. The screen does its job and then calls pop() to close. Whatever is passed to pop is returned to the code that had called push in the first place.

This still works with app_state package, because Navigator may contain not only app_state’s pages, but any routes. But then in Screen2 you don’t get URLs, browser navigation, and many useful things.

With app_state, we do not use Future for screen results. This is because in Dart futures do not survive page reloads. If we need to re-construct the page stack from a URL alone, the bottom page would not get to the state where it awaits something from the top page.

So we use events instead of futures. The example project can be found here.

On the “About” tab we show who this software is licensed to. On button click, we show a screen to enter the name. When the name is saved, it is passed back to the first screen and shown.

What is unusual is that we can start the app at the URL of the second screen, enter the name, and it will be passed to the “About” screen.

Here is lib/pages directory with new files highlighted:

How screens get closed

Each PageBloc has events stream. PageStackBloc listens to its pages’ event streams, and this allows page blocs to coöperate.

The bloc can call pop(data) method with an optional data argument. It emits the PageBlocCloseEvent that carries this data. Under the hood, Android back button calls this method without data. Also under the hood, the back arrow button in AppBar implicitly triggers the same event if used under PageStackBlocNavigator.

PageStackBloc receives this event and calls didPopNext method on the PageBloc immediately under. The event is passed there. This way, the bloc learns that it is likely the topmost one now.

Also PageStackBloc completes the future that was created with the original push.

So the bottom bloc has two ways to receive the data.

Let’s look at the code.

The input screen

The input screen must have a bloc for many reasons: the screen has a state (the TextEditingController), and the bloc must emit the close event with data.

We use PageStatefulBloc for this. It is PageBloc that also has states stream in addition to events. It makes a classic bloc:

It has TextEditingController to store the input state. In the constructor, we add a listener to this controller so that when the text changes, the bloc emits a new state. For this, we use emitState() method of PageStatefulBloc. It emits whatever createState() method returns.

The state is simple. It only has a bool to enable or disable the Save button.

The bloc also has onSavePressed() method which the screen will call when the button is pressed. It calls closeScreenWith and passes the event.

The bloc also has initialState field for convenience. It allows the screen to assume there is always some state and not handle the case where states stream has null for data.

The screen itself is simple:

It gets rebuilt when the bloc emits a new state.

It has ‘Cancel’ button that is always active and calls bloc.pop(), the built method that sends PageBlocCloseEvent with no data.

It has Save button that is only active if the user’s name is not empty. It calls the bloc’s onSavePressed that sends a close event with data.

Also note that InputPath has overridden defaultStackPaths getter, just like we did with book details page. It allows it to have the ‘About’ screen under it when the URL is typed in.

The opener screen

The About screen has also got its bloc for many reasons: it has a state now, it has to open the input screen, and it has to listen to the input screen’s result. The only notable thing is didPopNext method:

And the screen is trivial.

Run the app and check the result. Copy the URL of the input screen and paste it in a new browser tab to see how it re-creates the page stack. Input a name and see it passed to the “About” screen.

This is it for now. Please like this package if you find it useful.

If you have navigation cases not covered in this series, please leave a comment, and I will look into it.

If you have any issues with app_state package, please leave a comment here or submit an issue in GitHub.

--

--

Alexey Inkin
Flutter Senior

Google Developer Expert in Flutter. PHP, SQL, TS, Java, C++, professionally since 2003. Open for consulting & dev with my team. Telegram channel: @ainkin_com