Using generated route for production grade Flutter app (WeChat clone)

Daniel
4 min readNov 18, 2021

In my previous article I have discussed the folder structure of my production grade large scale flutter app. The article can be found here.

In this article I will share how I have laid the foundation by wiring generated route class with the main start up class to use the Bloc state management pattern along with scalable routing scheme.

State management and Bloc Access

Blocs are required to hold the business logic and manage state with respect to events generated by users while they interact with the app.

Some states are scoped to single page, others may stay within a route and yet others can be global. States like authentication and app theme are global that need to be available throughout the app. E.g. a user’s login status is required everywhere in the app.

To address the above scopes of states in an app the bloc library has the following access levels:

Local Access :- use BlocProvider to make a bloc available to a local sub-tree. In this context, local means within a context where there are no routes being pushed/popped.

A new bloc instance is provided.

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

class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: BlocProvider(
create: (BuildContext context) => CounterBloc(),
child: CounterPage(),
),
);
}
}

Anonymous Route Access:-use BlocProvider to access a bloc across routes. When a new route is pushed, it will have a different BuildContext which no longer has a reference to the previously provided blocs. As a result, we have to wrap the new route in a separate BlocProvider

The bloc passed from the local context is captured and provided to the next route using BlocProvider.value .

class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counterBloc = BlocProvider.of<CounterBloc>(context);
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute<CounterPage>(
builder: (context) {
return BlocProvider.value(
value: counterBloc,
child: CounterPage(),
);
},
),
);
},
child: Text('Counter'),
),
),
floatingActionButton: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: 5.0),
child: FloatingActionButton(
heroTag: 0,
child: Icon(Icons.add),
onPressed: () {
counterBloc.add(Increment());
},
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5.0),
child: FloatingActionButton(
heroTag: 1,
child: Icon(Icons.remove),
onPressed: () {
counterBloc.add(Decrement());
},
),
),
],
),
);
}
}

Named Route Access:-use BlocProvider to access a bloc across multiple named routes. When a new named route is pushed, it will have a different BuildContext (just like before) which no longer has a reference to the previously provided blocs.

The bloc provided to the next route using BlocProvider.value .

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

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

class App extends StatefulWidget {
@override
_AppState createState() => _AppState();
}

class _AppState extends State<App> {
final _counterBloc = CounterBloc();

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
routes: {
'/': (context) => BlocProvider.value(
value: _counterBloc,
child: HomePage(),
),
'/counter': (context) => BlocProvider.value(
value: _counterBloc,
child: CounterPage(),
),
},
);
}

@override
void dispose() {
_counterBloc.close();
super.dispose();
}
}

Generated Route Access:-we’re going to create a Router and use BlocProvider to access a bloc across multiple generated routes. We're going to manage the blocs which we want to scope in the Router and selectively provide them to the routes that should have access.

We will see how I used generated route access in my project below.

Global Access:-this makes a bloc instance available to the entire widget tree. This is useful for specific cases like an AuthenticationBloc or ThemeBloc because that state applies to all parts of the application.

We wrap the material app inside a BlocProvider to make it available globally.

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

class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => CounterBloc(),
child: MaterialApp(
title: 'Flutter Demo',
home: CounterPage(),
),
);
}
}

Using generated routes for clean routing and state management

For an app that is the size of WeChat with a lot of features deciding clean routing scheme is good as it will be a good investment for future application code maintenance. For this I have selected the generated rotes to manage my application routes or navigation. Along with generated routes, I have used generated route bloc access to manage application state.

I have decided , after reading bloc documentation and other articles, that each significant feature like chat, login or register should have its own route and manage its own state.

Later I will refactor the code to add global access blocs such as authentication and theming. The basic code will not change much.

As I continue to develop the application features there will be parts with local bloc access whose state remains within a single context.

All of the future progress and changes in the application will be shared in this platform. Now let me show you what I have done so far.

Application entry point

The main method which is the app entry point runs the App class which instantiates and loads the generated routes defined in the AppRouter class .

Application entry point

The AppRouter class defines the generated routes. Currently it has few routes but as the application adds new features routes will be added in this class.

Blocs that manage the state and business logic are instantiated here and provided to each route using BlocProvider.value. The AppRouter class should dispose the blocs when it disposes itself because the routes that receive the bloc can’t dispose a bloc they did not create locally.

Generated routes

Thank you for reading.

Share to others on your social network and follow me on Twitter

--

--