Journey towards Flutter Web Part 2: Routing with Fluro

Ashutosh Singh
Flutter Clan
Published in
5 min readApr 29, 2021

--

Photo by Max Avans on Pexels

If you are coming to this page directly, make sure you go through Journey towards Flutter Web Part:1 before starting this one.

Now you may be wondering 🧐 why do we need to handle the routes again?

Try refreshing any screen in your project on the web, you will observe that the whole project builds again and your app starts from the very first screen, or you may also observe a red screen as I did when I refreshed the LocationScreen in the aqi_monitor project.

Let’s analyze why this could have happened?

What is happening under the hood is that the data persists only till the time we refreshed our Flutter project. All those values which we passed to a specific screen through constructors become null as there is no previous context. Similar problems start occuring with State management packages like provider as our Provider objects are returned to their base state.

If you have set up routes using Navigator 1.0, you should migrate to Navigator 2.0 which supports web, or use a package like fluro.

How can Fluro solve this problem?

Fluro provides some features out of the box which are:

  • Query parameter parsing
  • Function Handlers for every screen
  • Works exactly same on both Mobile and Web
  • Easy to add a transition between screens based on Platform type

We can follow the below steps for setting up Fluro in our project:-

  1. Add dependency for fluro in pubspec.yaml file
dependencies:
fluro: 2.0.3

After adding dependency run flutter pub get

2. Create FluroRoutes class inside lib/routes/ as fluro_routes.dart

/// setting up singleton class for FluroRoutes

class FluroRoutes with FluroRouteHandlers {

/// initializing FluroRoutes
static final FluroRoutes fluroRoutesInstance = FluroRoutes._();

factory FluroRoutes() {
return fluroRoutesInstance;
}

FluroRoutes._();

void routes(FluroRouter fluroRouter) {
TransitionType transitionType;

/// setting transition on web
if (kIsWeb) {
transitionType = TransitionType.fadeIn;
}

/// setting transition on android
else if (Platform.isAndroid) {
transitionType = TransitionType.material;
}

/// setting transition on iOS
else {
transitionType = TransitionType.cupertino;
}

/// linking handlers and routes of screen
fluroRouter.define(
MyHomePage.routeName,
handler: homeScreenHandler,
transitionType: transitionType,
);

fluroRouter.define(
LocationScreen.routeName,
handler: locationScreenHandler,
transitionType: transitionType,
);
}
}

Here we have created a singleton class for FluroRoutes and we have declared a transition type that will depend on OS and declared all screen routes of the project and linked with their specific handlers.

3. Create FluroRouteHandlers inside lib/routes/ as fluro_route_handlers.dart

mixin FluroRouteHandlers {  /// Handler for MyHomePage
var homeScreenHandler = Handler(
handlerFunc: (
BuildContext context,
Map<String, List<String>> parameters,
) {
return MyHomePage();
},
);
/// Handler for LocationScreen
var locationScreenHandler = Handler(
handlerFunc: (
BuildContext context,
Map<String, List<String>> parameters,
) {
return LocationScreen();
},
);
}

Here, a handler's role is to provide the required parameters(which we will see later in this post) and launch the screen.

4. Setup FluroRoutes in main.dart

Setup your FluroRoutes in the MaterialApp.

class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
final FluroRouter fluroRouter = FluroRouter();

@override
void initState() {
super.initState();

/// setting up FluroRoutes with fluroRouter
FluroRoutes().routes(fluroRouter);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "AQI Monitor",
home: MyHomePage(title: 'AQI Monitor'),

/// pass you initial screen's route in initialRoute
initialRoute: MyHomePage.routeName,

/// use fluro's generator
onGenerateRoute: fluroRouter.generator,
onUnknownRoute: (unknownRoutes) {
return MaterialPageRoute(
builder: (context) => MyHomePage(),
);
},
);
}
}

On the Web, since we can go to another screen just by changing the URL and that will take you to an unknown screen that is not present in your project, so we can either redirect to one of the initial screens(e.g. splash screen, home screen) in a project or make a custom 404 screen in your project like this, which we can utilize inside onUnknownRoute.

5. Updating Navigation process

Now that we have properly set up Fluro, we will now update our navigating process for each screen.

i) Use Navigator for simple navigation.

Navigator.pushNamed(context, LocationScreen.routeName);

Don’t use MaterialPageRoute for navigation between screens, use only Navigator.

ii) Use QueryParams for sending data

Modify your model and arguments like this:

class CityModel {
String city;

CityModel({
this.city,
});

CityModel.fromJson(Map<String, dynamic> json) {
city = json['city'];
}

Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['city'] = city;
return data;
}

String toQueryParam() {
return Uri(queryParameters: toJson()).query;
}

CityModel.fromQueryParam(Map<String, dynamic> jsonMap) {
city = jsonMap['city']?.first;
}
}

toQueryParam is our helper function above, which converts our model’s JSON into query format. e.g. {“city”:”Delhi”} will be converted to city=Delhi

fromQueryParam is another helper function that converts our query params back to the model.

Now concatenate screen route name and query param with “?”(compulsory) sign.

Navigator.pushNamed(
context,
LocationScreen.routeName + '?${cityModel.toQueryParam()}',
);

In the last step, we will now modify our handlers by using our helper function. Query params which we passed in Navigator, we will convert them into our model in handlers using fromQueryParam helper function and pass that model or its values in our screen.

var locationScreenHandler = Handler(
handlerFunc: (
BuildContext context,
Map<String, dynamic> parameters,
) {
CityModel cityModel;
if (parameters != null && parameters.isNotEmpty) {
cityModel = CityModel.fromQueryParam(parameters);
}
return LocationScreen(
city: cityModel != null ? cityModel.city : null,
);
},
);

Now, run your project on the Web and also try to refresh a screen in the same way as before.

You will observe that our screen remains on the same screen event after refreshing and also the red screen that was coming before should disappear because the query params remain in the URL and Fluro passed those query params to our screen via the handler and this setup also runs smoothly on both Android and iOS.

So that’s the advantage of setting this new whole routing mechanism. 😎

home_screen.dart
location_screen.dart

IMPORTANT: Please make sure to handle all arguments and models in handlers which we are sending from one screen to another, you just need to convert them into query params, add them with the route while navigating, and then decode from query params back to the model.

In the next article, we will be discussing approaches to make UI responsive on the Web and on the Mobile by maintaining the same codebase.

If you find this article useful please do hit the clap icon 👏 and share it with your friends, as it will motivate me to write more.

Feel free to connect with me on social media platforms for any doubts regarding Flutter.

--

--