Photo by Andrew Neel on Unsplash

Flutter Navigation with Redux

Marcin Oziemski
4 min readFeb 14, 2019

--

In this article, I will describe how I handled navigation in the Flutter app with Redux. You could of course ask why I’ve done this and the answer would be mainly because of easier debugging and logging to analytics than in normal approach. However, this also enables more complicated navigation cases like “navigate to this position only if…”, and it would be separated from other logic layers.

What you should know to get the most from this article?
For sure know basics about Flutter and be familiar with Redux (or MVI architecture).

So let’s start!

App

To show you how I’ve done it I will use an example of the application for managing some games (like tennis or squash).

On the left, you can see that we have some main view with a list of games. The bottom navigation bar has also 3 other icons (then home) that lead to some stub view and a floating action button which open a new screen with a button to show a dialog.

AppRoutes

Each of these screens is represented by a String.

Which we use in our main Widget.

main()

Ok so firstly those routes names we use in the _getRoute(…) method to return CustomRoute with proper screen Widget on a route change.

We use navigationKey to be able to navigate without the need of context (which will be handy in NavigationMiddleware) like this:

navigatorKey.currentState.pushNamed(routeName)

NavigationMiddleware which we pass to store instance is responsible for listening for route change events in app state. Using navigatorKey it navigates to the proper screen (I will describe it in more detail later).

Next thing is routeObserver which observe Flutter Navigator events. It will help us with some default navigation events, but we will come back to it.

I know it’s a lot to proceed, but as a starting point, a lot of things initialize here that I will describe in details later on. Let's focus on the Redux part and start with the state of an application.

AppState

State of our application represents what we see on our screens. So logical is to put here information about our position in the app navigation stack.

I’ve represented that by a list of Strings named route. It’s like a back stack, so the last element on the list has a name of our current screen and the previous one represent the parent screen name. For example:

route[0] = “/”, route[1] = “/addGame”

Now it’s time to see how we will change that route property so let’s move to reducers.

Reducers

Our main reducer is pretty straight forward, each field has it’s own reducer.

NavigationReducer is the part that we are interested in.

We declare three actions: NavigationReplaceAction, Push and Pop. Replace and Push have a routeName property that accordingly replaces or adds a route to our route list in AppState. Pop just remove the last item from the list.

So now we can add an onPressed listener to our navigations button which will dispatch one of those actions. For example:

However, that will only change the route list in the AppState. It won’t navigate to proper screen. That's why we added NavigationMiddleware to our store.

NavigationMiddleware

Here we listen to specific actions that are dispatched on the store and launch side effects like navigation changes.

Here we use that navigationKey we declared in the main Widget. We needed to do it as here we don’t have access to the context. I’ve added checks to prevent calling navigation to the same place (which for example could launch animation again). Of course, that is only my implementation and yours could have different checks or none.

As you could notice we don't have here NavigatePopAction. That’s because we want to use out of the box navigation parts Flutter give us. For example, when we call to push NewGame screen to navigation with AppBar Widget in it, Flutter will automatically add there back arrow to navigate to parent screen.

We don’t want to override everywhere those clicks and dispatch our actions, as it’s unnecessary work. However, we still want to track our route in AppState. That's why we added routeObserver in our main Widget. To track if Flutter Navigation was “Poped”.

RouteObserver

Below is routeObserver with RouteAwareWidget which is needed to track route changes. The key part here is RouteAware interface that is for objects that are aware of their current Route.

Thanks to that we can now dispatch Pop action whenever we observe it in this Widget. Of course, we need to add it as root to each of our screens, but we can do it on the navigation level, like in the example below.

So that’s all

If something is unclear or too complicated I made a public repository with my example that I hope will help you. It’s under development so some other things could be “in progress”.

Next, you could add analytics or crash reporting to middleware that would send the state of your app (with route inside) for better understanding bugs or users experience.

P.S. This article shows only my attempt to resolve this case, but if you know how to improve it let me know :)

--

--

Marcin Oziemski

Senior Android and Flutter Developer. Working at Netguru.