Flutter Navigator 2.0 for mobile dev: Bloc state management integration
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:
- Flutter Navigator 2.0: 101 for mobile dev: Were to start if you have no idea what navigator 2.0 is.
- Flutter Navigator 2.0 for mobile dev: Nested navigators basics: How to nest the navigators, and the fix needed to avoid that they ruin your animations
- Flutter Navigator 2.0 for mobile dev: Transitions: How to implement Hero or Page route transitions with navigator 2.0
- Flutter Navigator 2.0 for web dev: Url handling: The basics of URL handling.
- Flutter web: URL handling made simple with simple_url_handler: How to use the simple_url_handler package to easily manage the url in flutter web.
- Flutter web: A complete example using simple_url_handler: A complete example, using everything seen above, to build the routing and url handling of a more complete app.
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:
- A bloc tutorial: find one on their website
- A navigator 2.0 tutorial: for this read my article “Flutter Navigator 2.0: 101 for mobile dev”
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 theRouterDelegate
- 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 {}