Flutter Navigator 2.0 for mobile dev: Bloc state management integration

Lulupointu
3 min readDec 24, 2020

--

This article is part of a series aiming at making you a master of Navigator 2.0 while showing you that it is not as hard as people think. The other articles of this series are:

State management is always a concern when building a medium to large size application. In Flutter, the bloc library is really popular. And today, we will see how to use bloc with navigator 2.0.

Note that this article is not:

This article is: how to merge the two together

What are we going to build?

The entire code can be found at the end of this article

We will use a bloc to represent the authenticate state of the user (authenticated or not). And we will use the Navigator to move between the Connection screen and the Home screen.

How to integrate the two?

There are two key elements here:

  • Ignore the notifyListeners method of the RouterDelegate
  • Use a BlocBuilder to rebuilt the Navigator when needed

And just like that you have a declarative navigation based on your bloc state.

Use BlocProvider.of<AuthenticationBloc>(context).add(UserLoginEvent()) when you need to login and that’s it!

Full code

Not prettified for easy copy/paste:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider<AuthenticationBloc>(
create: (_) => AuthenticationBloc(),
child: MaterialApp(
home: Router(
routerDelegate: MyRouterDelegate(),
),
),
);
}
}

class MyRouterDelegate extends RouterDelegate
with ChangeNotifier, PopNavigatorRouterDelegateMixin {
bool showOtherPage = false;
final GlobalKey<NavigatorState> navigatorKey;

MyRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>();

@override
Widget build(BuildContext context) {
return BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, authenticationState) {
return Navigator(
key: navigatorKey,
pages: [
MaterialPage(
key: ValueKey('MyConnectionPage'),
child: MyConnexionWidget(),
),
if (authenticationState is AuthenticatedState)
MaterialPage(
key: ValueKey('MyHomePage'),
child: MyHomeWidget(),
),
],
onPopPage: (route, result) {
if (!route.didPop(result)) return false;

BlocProvider.of<AuthenticationBloc>(context).add(UserLogoutEvent());
return true;
},
);
},
);
}

// We don't use named navigation so we don't use this
@override
Future<void> setNewRoutePath(configuration) async => null;
}

class MyConnexionWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Navigator 2.0 101 - Connexion screen'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlatButton(
child: Container(
padding: EdgeInsets.all(8.0),
color: Colors.greenAccent,
child: Text('Click me to connect.'),
),
onPressed: () =>
BlocProvider.of<AuthenticationBloc>(context).add(UserLoginEvent()),
)
],
),
),
);
}
}

class MyHomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Navigator 2.0 101 - Home screen'),
),
body: Center(
child: Text('You are connected!'),
),
);
}
}

class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> {
AuthenticationBloc() : super(UnauthenticatedState());

@override
Stream<AuthenticationState> mapEventToState(AuthenticationEvent event) async* {
switch (event.runtimeType) {
case UserLoginEvent:
yield AuthenticatedState();
break;
case UserLogoutEvent:
yield UnauthenticatedState();
break;
}
}
}

// Authentication events
abstract class AuthenticationEvent {}

class UserLogoutEvent extends AuthenticationEvent {}

class UserLoginEvent extends AuthenticationEvent {}

// Authentication states
abstract class AuthenticationState {}

class UnauthenticatedState extends AuthenticationState {}

class AuthenticatedState extends AuthenticationState {}

--

--