Geek Culture
Published in

Geek Culture

Flutter Navigator 2.0 for Authentication and Bootstrapping — Part 5: Web

Wizard Dashatar

In the previous parts of this series, we focused on building a navigation stack using the Navigator 2.0 API for the following cases:

  • Application state changes caused by user interactions, authentication state update, and bootstrapping.
  • Popping the current route requests from the operating system (OS).

In this article, we will focus on two things:

  1. Updating the app state and the navigation stack according to the new route information coming from the OS.
  2. Reporting new route information to the OS according to the app state.

Fasten your seatbelts because things will get dirty in this part!

Web Experience

We parse the Web URL that is stored in the location field of RouteInformation to provide a good user experience for Web users. The previous samples work well for mobile applications. However, they provide a poor Web experience because the browser’s address bar is not updated correctly when the app state changes. See the recording below showing the previous article’s sample app running on the Web.

Now, let’s see how we can provide a more powerful Web experience by enabling application navigation with the address bar.

Note that the sample apps in this series do not focus on improving the loading time and screen transitions.

Router Delegates

You may feel like the router delegates complicate the picture but in this article, we will only focus on the routeNameParser and the routerDelegate. Although the routeNameProvider delegate can be customized to change the way of obtaining the route name (URL) from the OS, in many cases, we don’t need to customize it. Therefore we will let Router widget use the default routeNameProvider implementation.

I don’t know why in the design docs the routeNameProvider and routeNameParser delegates are named differently than their actual class names which are RouteInformationProvider and RouteInformationParser . I will use corresponding class names in this article when referring to these delegates.

This first part of the article is more theoretical. I would suggest staying patient until the second part which shows how this theory is practiced in the sample app. You don’t need to understand all the details at first. You can read the theory part again after completing this article. Hopefully, in the end, you will be able to use the Navigator 2.0 API in Web applications without suffering.

1- Information Flow

1.1 RouteInformation

RouteInformation holds location and state information of a route. The location field is a String and it is equivalent to a Web URL. In this article, we will focus on the location field.

In the mobile and desktop applications, it is usually enough to make RouteInformation flow one-way: from the operating system (OS) to the Router widget. When the navigation stack is updated as a result of an app state change, the operating system usually doesn’t need to know the location information (URL) of the current route.

In order to be able to show the correct URL on the Web browser’s address bar, the navigation stack updates caused by the app state changes should be reported from the Flutter framework layer to the Flutter Web engine layer. This is why the RouteInformation flow needs to be two ways for Web applications.

Unlike mobile apps, Flutter Web apps do not communicate with the OS since they are sandboxed in the Browser application. In this article, we will talk about the communication between the OS and the Router widget to stay consistent with the design docs, and simplicity. In the end, Navigator 2.0 API is implemented in the framework layer and it doesn’t need to know the engine layer details.

1.2 RouteInformationParser

Our customized RouteInformationParser is the critical class for the two-way RouteInformation flow since the location information parsing and restoring logic is implemented in this delegate.

Flow 1: From OS to Router

Flow 1: OS to the Router widget

Flow 2: From the Router widget to the OS

Flow 2: From Router to OS

1.3 RouterDelegate

In order to be able to utilize the RouteInformationParser component of the Navigator 2.0 API, we need to override RouterDelegate 's setNewRoutePath method and currentConfiguration getter.

setNewRoutePath:

In Web apps, when the back or forward button is pressed, or when a new URL is entered in the browser’s address bar, the flow 1 mentioned above (OS to Router) starts. The role of the RouterDelegate in the flow 1 is updating the app state before building the next navigation stack.

When the Router widget interprets the OS´s intention with the help of its RouteInformationParser delegate, it calls the setNewRoutePath of the RouterDelegate . We should override this method to adjust the app state according to the requested configuration so that the next navigation stack is built accordingly.

currentConfiguration:

The role of the RouterDelegate in the flow 2 (Router to OS) is providing the currentConfiguration to Router widget. Then theRouter widget restores the RouteInformation with the help of its RouteInformationParser delegate.

  • If currentConfigurationgetter method is not implemented, the Router widget doesn't report the RouteInformation and the address bar is not updated. This is the case when we use the old imperative navigator API.
  • If currentConfiguration implementation doesn’t reflect the current app state, the browser backward/forward buttons will not work properly.

2- Navigator 2.0 in Action!

It has been too much information to digest. Let’s keep the motivation high because if we achieve the correct navigation implementation we will have one more platform to support: WEB!

Dashatar

2.1 The App

In the previous samples, we injected theRouter widget as the home of the MaterialApp . See the Router widget in the below widget tree between the MaterialApp and the Navigator widget.

In this sample, we use the MaterialApp.router constructor and provide the RouterDelegate and RouteInformationParser to the Material app. Therefore, we don’t instantiate and inject the Router widget into the app. Instead, the app uses the delegates internally. Notice in the below widget tree that we don’t see the Router widget in between the MaterialApp and the Navigator widget.

2.2 The Custom Data Type: `MyAppConfiguration`

The custom data type is utilized by the Router widget to translate the RouteInformation to app state and report the app state as RouteInformation to the OS. I believe this part is the reason why many developers don’t like the Navigator 2.0 API because it is totally up to developers how to define this class. Thus, we need to be smart here and take every detail into consideration.

In the sample app, I used four fields and declared named constructors to represent each configuration. For example:

  • If the loggedIn field is null, the last route in the navigation stack should be the Splash page.
  • If the loggedIn field is set to false, then the app configuration should tell the Router widget that the last route on the navigation stack should be the Login page.
  • If the loggedIn is true , colorCode is set, but the shapeBorderType is not set, the last route should be the ColorPage .
  • If the loggedIn is true , colorCode is set, and the shapeBorderType is set, the last route should be the ShapePage .
  • We show an error page if the unknown state is set to true.

2.3 The RouteInformationParser

We need to override the two methods of the RouteInformationParser each used in one flow between the Router widget and the OS.

Flow 1: OS to the Router widget via parseRouteInformation:

In this method, we need to generate a configuration from the given RouteInformation that is provided by the default RouteInformationProvider delegate. Note that Flutter uses the hash (/#/) location strategy by default. In this sample, we configure the URL Strategy to the PathUrlStrategy .

We parse the URL which is stored in the location field of the RouteInformation . It is up to us how we associate the URLs with the configurations. Here is my URL strategy:

  • The Home page is the initial route which is represented with `/ `by default. If the URL parsing doesn’t return any path segment, then we are at the home page and the returned configuration is instantiated using MyAppConfiguration.home(). Note that unless we customize the RouteInformationProvider the initial route path is always `/ `. It may change for other OSs though, but for Web and mobile it is `/ `
  • If the number of the path segments is 1, we allow accessing to the home page or to the login page. For example, if the user appends home String to the domain such as http://localhost:59358/home then the returned configuration should be instantiated by calling MyAppConfiguration.home() . If the path segment is login , the configuration should be instantiated by calling MyAppConfiguration.login() .
  • If the number of the path segments is 2 we return either a MyAppConfiguration.color() or MyAppConfiguration.unknown() configuration. The first path segment should always be the colors String and the second segment is always the color code such as http://localhost:59358/colors/cddc39. Here we know that if the first path segment is not colors or the second segment is not a hex color code then we should return the MyAppConfiguration.unknown() configuration.
  • If the number of the path segments is 3, we forward either a MyAppConfiguration.shapeBorder() or MyAppConfiguration.unknown() configuration. The rules for the first and second path segments apply here too. We expect the third path segment to be the name of the shape border type. Since the list of shape borders are the same for all the users and we are not fetching them asynchronously, we validate the border type in RouteInformationParser .

I would like to clarify more how we show the LoginPage or the HomePage when the URL doesn’t have path segments. At first, injecting the AuthenticationRepository to the RouteInformationParser class sounds like the easiest way to obtain the authentication state and make a decision accordingly. However, I personally don’t prefer handling async data operations in RouteInformationParser considering the concerns regarding the asynchrony mentioned in Router widget documentation.

I think the role of the RouteInformationParser is to interpret the intention as much as possible. The intention will be passed to the RouterDelegate which has the last word when building the navigation stack, so it is ok for RouteInformationParser to return the wrong app configuration. The RouterDelegate is ready to correct according to the application state.

In our implementation, when the parsed RouteInformation is / , we return MyAppConfiguration.home() without knowing the authentication state because the intention of the user is actually seeing the home page. If the user is not logged in, we should show the login page.

Similarly, when the intention of the user is to see the ColorPage or the ShapePage we don’t know if the hex color code parsed from the URL is included in the color list because fetching the color list is async operation and we don’t want to do it in RouteInformationParser class. We anyway pass the intention to the RouterDelegate . It will update the app state according to the interpretation and build a corresponding navigation stack.

Flow 2: Router widget to the OS via restoreRouteInformation:

This one is much simpler compared to parsing route from the RouteInformation . In this flow, we only need to construct RouteInformation with a URL for a given configuration.

In our app, if the app configuration is Splash , we don’t want to change the URL. In this case, we will return null . Unfortunately, as of today, the API throws an exception silently when null is returned.

2.4 RouterDelegate

RouterDelegate is the heart of the big picture. In this class, we structure the navigation stack considering the app states. In our sample app, the app state is represented by _show404 , _loggedIn , _colors , _selectedColorCode , and _selectedShapeBorderType properties. Whenever one of these fields are updated, the RouterDelegate notifies the Router widget and then the Router widget calls the build method of the RouterDelegate .

Flow 1: OS to the Router widget via setNewRoutePath:

The RouterDelegate receives a configuration after RouteInformation parsing via this method. This configuration is the representation of operating system’s intention. Thus, we will update the app state according to this intention.

In our implementation, we use setter methods for each field that represents the app state. Each setter method notifies the Router widget that the app state has changed.

  • If the result of the parsing returns the unknown configuration, we set the show404 to true. If the show404 value is true, _selectedColorCode , and _selectedShapeBorderType states are set to null inside the setter method of the show404before the new build.
  • If the interpreted configuration is the Login page, Splash page, or the Home page, we set show404 to false, _selectedColorCode , and _selectedShapeBorderType to null before the new build.
  • If the configuration is determined to be the Color page, we set show404 to false, _selectedColorCode to the parsed color code, and _selectedShapeBorderType to null before the new build.
  • If the configuration is the Shape page, we set show404 to false, _selectedColorCode to the parsed color code, and _selectedShapeBorderType to null before the new build.

In the previous section, we mentioned that the RouteInformationParser may return wrong app configurations since we don't prefer to perform async operations inside it. In RouterDelegate class, we will fetch the login state and the color list asynchronously during its initialization so that we keep the correct state in the RouterDelegate and build a navigation stack accordingly. For example, when setting the _selectedColorCode we set the show404 state to true if the parsed hex color code is not contained in the colors list. Another example is when building the navigation stack, we show either the _loggedInStack or the _loggedOutStack according to internal _loggedIn state no matter RouteInformationParser interprets the intention as home page, color page, or shape page.

Flow 2: Router widget to the OS via currentConfiguration:

In this getter method, we will reflect the app state to the currentConfiguration so that the Router widget passes this configuration to the RouteInformationParser and the parser returns a RouteInformation (URL) for the use of OS. We return all the possible configurations to the Router but it is up to the RouteInformationParser to reflect the state as a URL in the address bar of the browser.

Conclusion

In this article, we learned:

  1. Building a navigation stack by parsing the Web URL links
  2. Updating the Web browser’s address bar according to the app state changes.

This is the last article in Flutter Navigator 2.0 for the Authentication and Bootstrapping series. In the Flutter for Single-Page Scrollable Websites with Navigator 2.0 series, you can read how to utilize the Navigator 2.0 API for a single page scrollable website using path variables and query parameters.

If you liked this article, please press the clap button, and star the Github repository. You can find the source code on the Github page. The project includes multiple main.dart files. The easiest way of running the sample app is right-clicking on the main_002_04.dart file and selecting the Run 'main_002_04.dart'.

Special thanks to Jon Imanol Durán who reviewed all the initial versions of these articles in this series and gave me useful feedback.

--

--

--

A new tech publication by Start it up (https://medium.com/swlh).

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Cagatay Ulusoy

Cagatay Ulusoy

Flutter and Android Mobile App Developer

More from Medium

How to use Flutter Time Series Charts with Firebase

Handling Firebase DeepLink Fallback for Desktop(website) in Flutter

TDD in Flutter Part 3: Testing your widgets

Migrating your Flutter project to null safety

Nusafety errors