Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nesting routes with flutter

I am trying to figure out good architectural solution for following problem: I have following First level routes that can also be referred to as layouts:

/onboarding/* -> Shows onboarding layout /dashboard/* -> Shows dashboard layout /overlay/* -> shows slide up overlay layout /modal/* -> shows modal layout 

User is routed to each of these depending on his/her auth state, actions etc.. I got this stage correctly.

Issues arise when I want to use Secondary level routes that can be referred to as pages, for example

/onboarding/signin -> Shows onboarding layout, that displays signin route /onboarding/plan -> Shows onboarding layout, that displays plan options /modal/plan-info -> Shows modal layout, over previous page (/onboarding/plan) and displays plan-information page. 

How can I best define / organise these in a way where I can efficiently route to layouts and pages they display? Note, that whenever I route pages inside one layout, layout is not changing, but I want to animate content (pages) that are changing inside of it based on route.

Thus far I achieved following

import "package:flutter/widgets.dart"; import "package:skimitar/layouts/Onboarding.dart"; import "package:skimitar/layouts/Dashboard.dart";  Route generate(RouteSettings settings) {   Route page;   switch (settings.name) {     case "/onboarding":       page = new PageRouteBuilder(pageBuilder: (BuildContext context,           Animation<double> animation, Animation<double> secondaryAnimation) {         return new Onboarding();       });       break;       case "/dashboard":       page = new PageRouteBuilder(pageBuilder: (BuildContext context,           Animation<double> animation, Animation<double> secondaryAnimation) {         return new Dashboard();       });       break;   }   return page; }  /* Main */ void main() {   runApp(new WidgetsApp(       onGenerateRoute: generate, color: const Color(0xFFFFFFFFF))); } 

This routes to on boarding and dashboard layouts (right now just simple Containers wrapping text). I also believe that I can use PageRouteBuilder latter on to animate transitions between routes? Now I need to figure out how to have something like nested secondary router inside on boarding and dashboard.

Below is somewhat of a visual representation of what I want to achieve, I need to be able to successfully route blue and red bits. In this example as long as we are under /dashboard blue bit (layout) doesn't change, but as we navigate from say /dashboard/home to /dashboard/stats the red bit (page) should fade out and fade in with new content. If we navigate away from /dashboard/home to say /onboarding/home, the red bit (layout) should fade away, along with its currently active page and show new layout for onboarding and the story continues.

enter image description here

EDIT I made a bit of the progress with approach outlined below, essentially I will determine layout inside my runApp and will declare new WidgetsApp and routes inside each of the layouts. It seems to work, but there is an issue, When I click "SignUp" I am redirected to correct page, but I can also see old page below it.

main.dart

import "package:flutter/widgets.dart"; import "package:myProject/containers/layouts/Onboarding.dart";  /* Main */ void main() {   runApp(new Onboarding()); } 

Onboarding.dart

import "package:flutter/widgets.dart"; import "package:myProject/containers/pages/SignIn.dart"; import "package:myProject/containers/pages/SignUp.dart"; import "package:myProject/services/helpers.dart";  /* Onboarding router */ Route onboardingRouter(RouteSettings settings) {   Route page;   switch (settings.name) {     case "/":       page = buildOnboardingRoute(new SignIn());       break;     case "/sign-up":       page = buildOnboardingRoute(new SignUp());       break;     default:       page = buildOnboardingRoute(new SignIn());   }   return page; }  class Onboarding extends StatelessWidget {   @override   Widget build(BuildContext context) {     return new Container(       decoration: new BoxDecoration(           color: const Color(0xFF000000),           image: new DecorationImage(               image: new AssetImage("assets/images/background-fire.jpg"),               fit: BoxFit.cover)),       child: new WidgetsApp(           onGenerateRoute: onboardingRouter, color: const Color(0xFF000000)),     );   } } 

SignUp.dart

import "package:flutter/widgets.dart";  class SignUp extends StatelessWidget {   @override   Widget build(BuildContext context) {     return new Center(         child: new Text("Sign Up",             style: new TextStyle(color: const Color(0xFFFFFFFF))));   } } 

helpers.dart

import "package:flutter/widgets.dart";  Route buildOnboardingRoute(Widget page) {   return new PageRouteBuilder(       opaque: true,       pageBuilder: (BuildContext context, _, __) {         return page;       }); } 
like image 279
Ilja Avatar asked Jan 04 '18 15:01

Ilja


People also ask

What is nested navigation in Flutter?

A Navigator widget in Flutter is what we use to maintain a stack of routes and it plays a huge role in helping us to navigate between routes. The Navigator widget often works under the hood without us having to initialise it but there are certain use cases where we may have to do it manually.

What are nested routes?

In my own words, a nested route is a region within a page layout that responds to route changes. For example, in a single-page application, when navigating from one URL to another, you do not need to render the entire page, but only those regions within the page that are dependent on that URL change.


1 Answers

While it's technically possible to nest "Navigator", this is unrecommended here (as it breaks Hero animation)

You can use onGenerateRoute to build nested 'routes', in the case of a route '/dashboard/profile', build a Tree WidgetApp > Dashboard > Profile. Which I assume is what you're trying to achieve.

Combined with a higher order function, you can have something that creates onGenerateRoute for you.

To provide a clue of the code-flow: the NestedRoute neglects the exact build of the layout, letting it in the builder method (e.g. builder: (child) => new Dashboard(child: child),). When calling the buildRoute method we will generate a PageRouteBuilder for the very instance of this page, but letting _build manage the creation of the Widgets. In _build we either use the builder as is - or let it inflate the subroute, by recalling the requested subroute, calling its own _build. Once done, we'll be using the built subroute as the argument of our builder. Long story short, you recursively dive into further path levels to build the last level of the route, then let it rise from recursion and use the result as an argument for the outer level and so on.

BuildNestedRoutes does the dirty job for you and parses the lists of NestedRoutes to build the necessary RouteSettings.

So, from the below example

Example :

@override Widget build(BuildContext context) {   return new MaterialApp(     initialRoute: '/foo/bar',     home: const FooBar(),     onGenerateRoute: buildNestedRoutes(       [         new NestedRoute(           name: 'foo',           builder: (child) => new Center(child: child),           subRoutes: [             new NestedRoute(               name: 'bar',               builder: (_) => const Text('bar'),             ),             new NestedRoute(               name: 'baz',               builder: (_) => const Text('baz'),             )           ],         ),       ],     ),   ); } 

Here you simply defined your nested routes (name + associated component). And NestedRoute class + buildNestedRoutes method are defined this way :

typedef Widget NestedRouteBuilder(Widget child);  @immutable class NestedRoute {   final String name;   final List<NestedRoute> subRoutes;   final NestedRouteBuilder builder;    const NestedRoute({@required this.name, this.subRoutes, @required this.builder});    Route buildRoute(List<String> paths, int index) {     return new PageRouteBuilder<dynamic>(       pageBuilder: (_, __, ___) => _build(paths, index),     );   }    Widget _build(List<String> paths, int index) {     if (index > paths.length) {       return builder(null);     }     final route = subRoutes?.firstWhere((route) => route.name == paths[index], orElse: () => null);     return builder(route?._build(paths, index + 1));   } }  RouteFactory buildNestedRoutes(List<NestedRoute> routes) {   return (RouteSettings settings) {     final paths = settings.name.split('/');     if (paths.length <= 1) {       return null;     }     final rootRoute = routes.firstWhere((route) => route.name == paths[1]);     return rootRoute.buildRoute(paths, 2);   }; } 

This way, your Foo and Bar components will not be tightly coupled with your routing system ; but still have nested routes. It's more readable then having your routes dispatched all over the place. And you'll easily add a new one.

like image 74
Rémi Rousselet Avatar answered Oct 09 '22 02:10

Rémi Rousselet