Flutter Navigation part 2

Dzulfaqar Aar
6 min readSep 5, 2022

--

Why do you need to manage the navigation globally? The answer is to avoid more boilerplate code on your application. Let me explain in more detail.

If you don’t know how to navigate between pages in the flutter application, you can read my article here.

Setup

First, you need to install mockoon application to mock server API. Second, you can download the starter app from this link. You can open the project and the structure should be like this.

You can open the terminal and then run flutter pub get. Then the lib and test folder will not show with red color, which means no more errors cause all dependencies have been downloaded.

Next, we need to import FlutterMockoon.json file into the mockoon application. Follow the steps below.

Open environment
Start server

We have 2 APIs POST /login and GET /profile. The login API will return a token with a random GUID and expire with the current time plus 5 minutes. The profile API will return id and name. You can modify the response as you need.

Flow of Application

Before running the application, open the file RemoteDataSource under lib/data/datasources folder and change the baseUrl with your IP Address and the port used in mockoon app. If you do not change the port it will be 3000 so the baseUrl should be http://{YOUR_IP_ADDRESS}:3000/, in my device is http://192.168.18.21:3000/. Run the app.

Let me explain the application flow. The first time the app will check if a user already logged in and then directly open the home page, if not then show the login page. From the home page, you also can click the logout button to back to the login page.

You can fill the username with email format and password, the data will be sent to server API then you get token and expire response. When you open the profile page, the app will check if the token does not expired and then call the /profile API. But, if you call the /profile API after 5 minutes, you will get a message that an error happened.

The example above is used to simulate if you have an expired token. Or another case you can try is when you call an API but the response give you status code 401 which means your request is Unauthorized. And then you should directly force the user to the login page again.

Status Code 401 with delay 2 seconds

What happens when the token expired?

As you see until this point, the application doesn't navigate the user to the login page. Let's make it happen.

When we want to call an API using RemoteDataSource, first we get the token using LocalDataSource but if it's expired it should be null.

The simple handle is in the profile_provider.dart file we check when failure happens.

if (failure is TokenFailure) {
// Navigate to LoginPage
}

And don’t forget to clear the shared preference by calling localDataSource.clear() in RepositoryImpl.

if (token == null) {
await localDataSource.clear();
return const Left(TokenFailure(serverFailureMessage));
}

For this solution, you can download it from this link.

Redirect user to LoginPage

What is the problem?

Now for every API call, we need to check the token is expired and call await localDataSource.clear(), and handle it in provider files that call the API. Can you imagine if you have more than 10 APIs, 20 APIs, or 30 APIs?

Can we directly navigate the user when we know the token is expired? Yeah, but the Navigation.pushAndRemoveUntil function needs a parameter BuildContext context. We need to pass the context from Provider until LocalDataSource like this Provider -> UseCase -> Repository -> LocalDataSource. I don’t think that's a good idea.

Solution

You need to manage the navigation globally. If you move your cursor into MaterialApp widget, there is GlobalKey<NavigatorState>? navigatorKey parameter that you can use.

First, create navigation.dart under the common folder and create a global variable named navigatorKey.

After that modify the main.dart file by adding navigatorKey parameter into MaterialApp.

return MaterialApp(
navigatorKey: navigatorKey,
.....
);

Now if you want to navigate to some page, change Navigator.push with Navigation.push, change Navigator.pushReplacement with Navigation.pushReplacement, and change Navigator.pushAndRemoveUntil with Navigation.pushAndRemoveUntil. So all the navigation will use global navigatorKey and you don’t need to pass BuildContext context.

We need to adjust some files to finish the code. Remove this part from the profile_provider.dart file.

if (failure is TokenFailure) {
// Navigate to LoginPage
}

Remove this part from the repository_impl.dart file.

if (token == null) {
await localDataSource.clear();
return const Left(TokenFailure(tokenFailureMessage));
}

Last, modify local_data_source.dart file like this.

Until this point, it will work as we want. But be aware if we redirect the user to the login page, in the Profile page there is still a Provider waiting for the result. So if the Future of this await getUser.execute(); came back to the Provider but the page is already closed, it will show you an error like this.

What can you do to avoid that error? You can delete ChangeNotifierProvider from the home_page.dart file when navigating to the profile page like this.

Then you can move the ProfileNotifier to the main.dart file and using MultiProvider like this.

return MultiProvider(
providers: [
ChangeNotifierProvider<ProfileNotifier>(
create: (_) => ProfileNotifier(getUser: di.locator()),
),
],
child: MaterialApp(
navigatorKey: navigatorKey,
.....
),
);

For the complete code solution, you can download it from this link.

Summary

The best practice is to keep the token valid by refreshing it when needed. But, if you face some condition that the token cannot be refreshed, you can redirect the user to the login page again.

Manage navigation globally is not only used for handling the token, another example is when you receive a notification and the user clicks it then you need to navigate the user to a specific page. You can use it also as default navigation between pages.

Hopefully, this article can help you understand more about the navigation state for the flutter application.

If you like what you read, please give me your clap 👏. Thanks for your support!

--

--