Recovering user input when ‘Forward’ is clicked in Flutter app

Alexey Inkin
Flutter Senior
Published in
3 min readJul 29, 2022

--

Use case: In a multi-screen app, the user enters some text, then closes the screen without saving by ‘Back’ button. They then click ‘Forward’ and expect their input is still there.

This could be done in many ways:

  1. A state object behind the scene. The input window should store changes there and get them when re-initializing. But that is complex. What if we have many URLs like /input/something all handled by the same screen? It’s too much work to pick the right preserved input.
  2. A database or local storage. Even more work.

A better way is to just use the browser History API to attach hidden information to the URL itself.

For this, we will use app_state package that is a router with BLoC state management built-in.

If you are not familiar with it, start with these tutorials:

  1. Instagram-like navigation with Router API in Flutter.
  2. Adding web URLs to Flutter app using app_state package.

The project source for this tutorial is here. The structure is:

Home screen is stateless and is of no interest.

InputBloc and InputPath are where the magic happens.

The bloc contains the text editing controller. On every change, it emits the new InputPath object with text in it. The text is not a part of the visible page path but it is attached to it in the browser history accompanying the current URL.

InputPath class puts this text into the state map that is actually stored:

When the user clicks ‘Back’, the page and the bloc are removed from the stack and disposed.

Then on ‘Forward’ click, tryParse method is called (as on any other navigation). If the URL matches /input, Flutter checks if the state was previously stored for this URL. If it was, app_state’s PageStackRouteInformationParser detects this and does not even try to parse the URL. Instead it recovers everything from it.

So if tryParse is called in InputPath, the state is definitely empty, so we create an object with empty text.

Then the bloc gets created, and setStateMap is called on it. If the state was recovered from browser history, it is passed there. This way the text is back to the controller.

If we had many routes like /input/something handled by a single screen, a history entry for each one would store its text independently.

Other things to store and recover like that:

  • Scrolling position in tall screens (add the ScrollController to bloc, listen to changes).
  • Text selection (store the whole TextEditingValue and not only the text).

Most of the job is done by Flutter Router API and app_state package.

You can actually do this with raw Flutter Router API, but app_state gives some advantages:

  • It recovers the the state so your path parsing does not even start if the state is found.
  • It manages the stack and allows to push and pop pages.
  • It serializes states of multiple blocs in one state. It just so happened that in this example we only have one screen to preserve.
  • It saves a few hundred lines of code.

So go ahead and start the app_state tutorial if you have not yet started.

What else?

What other navigation scenarios do you struggle with? Tell me in the comments, so I can make more tutorials.

--

--

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