Taking the right route with Flutter.

There is a lot of good documentation and articles about the navigation, firebase dynamic links, and state management in Flutter. While all of them are clear and understandable in isolation, many developers may find themselves struggling to “put it all together” in the real-world application.
One of the problems with deep linking examples is that it’s not always as straightforward as pushing a specific route with the parameters, extracted from the deep link. For example, the deep link might only be available for users, who are logged in, in which case the user has to go through the login flow before opening the deeply linked widget.
I am sure a lot of you have noticed this by tapping on the link for some app, passing the login process only to realize the app has lost track of the link you have tapped and landed you on the home screen, making you tap the link again.
The problem
Route user to the specific screen in the app after they tap the app deep link. If any higher-priority steps such as authentication require taking care of — perform them, keeping in mind that the deep link has to be opened eventually.
Our toolbox
- Firebase Dynamic links
- BLoC library for state management
NB: It is assumed the reader is familiar with Flutter state management & specifically with the BLoC library. If you are not yet — I encourage you to do so- it’s really good stuff!
Let’s get to the code.
If you can’t wait to see what we end up with, here is the complete project.
If you want to go through this step-by-step in more detail, let’s get started!
Creating the app launcher.
Our app will always begin its life from the app launcher widget (a MaterialApp), which will push the required root widget, depending on the current app state. The flow we will be trying to achieve is this:

In real-world apps there might be extra onboarding steps aside from the login (accepting T&Cs, setting the username or photo, completing the onboarding tutorial) but we will stick just to the login flow to keep our example simple but keeping in mind that is has to be extendable for other use cases.
We will first define AppRoute
class, which will represent possible routes the app can take (in our case either the home route or the details screen):
Define the set of possible launch states, the app can be in:
We will also define AuthRepository
. Authentication details are likely to differ from app to app and are not relevant to this article so let’s only focus on its interface:
Let’s now build an AppLaunchBloc
:
This BLoC will either accept CheckLoginStatus
event, which will set the optional deep link and refresh the launch state or RefreshLoginStatus
event, which will simply refresh the launch state. It is also important to note that we are keeping our deep link around in AppLaunchState.deepLin
field until we end up in the state, in which we can open it (which is AuthenticatedState
for this app).
What we need now is the actual UI widget, which will submit these events to the AppLaunchBloc
and react to its state changes by pushing the required widget, which brings us to the AppLauncher
:
That snippet interspersed with a few comments, which hopefully make it more clear.
So far so good, we now have an AppLauncher
widget, which pushes the required route, depending on the state, emitted by the AppLaunchBloc
.
If user is not authenticated, AuthPage
widget is pushed, which looks like this:
Its BLoC is omitted as it’s not relevant to the point we are about to discuss. What’s important is that after user signs in, we effectively remove any widgets until the root and then open the launcher after user signs in:
Basically, we are going back to AppLauncher
, asking it to refresh the app state (which will now be logged in), which will make UI open the appropriate widget (either HomePage
or DetailsPage
for the deep link).
One could argue that we could launch the HomePage
directly from the AuthPage
. We might as well pass the deep link to the AuthPage
and handle it there too. This is certainly an option but brings a couple of problems:
- The routing responsibility will be “smeared” across multiple widgets (
AppLauncher
andAuthPage
), which is harder to keep track of and maintain. - Remember that this is a “toy” example with only login flow. As you add more pages (optional username set up after login, agreeing to T&Cs, onboarding video or animation), things get more complicated. Each page will have to “decide” where to go next & the routing functionality will get even more spread out across multiple widgets.
Conclusion
First of all, congratulations on getting that far into this article!
We have discussed one of the ways to handle the app deep links, making sure it persists through the login flow or potentially any other flow the user may have to go through on the first app launch. I encourage you to clone and play with the complete source of the sample app for this article
This is definitely not the only way to handle this so if you have a better idea or found a bug/issue in the article or the code — please, put that down in the comment :).