Flutter persistent sidebar

Viewed 795

In my application I want to have a sidebar that allows me to have access to specific functions everywhere in my application.


What I want :

  • That the sidebar remains visible when I push my pages
  • That I can pushNamed route or open a modal with one of the sidebar functions
  • That I can not display the sidebar on certain pages

What I do :

enter image description here

In red, the persistent sidebar and in yellow my app content.

enter image description here

If I click on my profil button in the HomeView, the ProfilView is displayed and my sidebar remains visible so it's ok

enter image description here

My AppView :

class AppView extends StatelessWidget {
  const AppView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {

    return MaterialApp(
      title: AppConfig.kAppName,
      debugShowCheckedModeBanner: false,
      theme: AppTheme().data,
      builder: (context, child) => SidebarTemplate(child: child), // => I create a template 
      onGenerateRoute: RouterClass.generate,
      initialRoute: RouterName.kHome,
    );
  }

My SidebarTemplate : (Display the sidebar and load the page with my router)

class SidebarTemplate extends StatelessWidget {
  final Widget? child;
  const SidebarTemplate({Key? key, this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        body : Row(
          children: [
            SidebarAtom(), // => My sidebar Widget
            Expanded(
              child: ClipRect(
                  child: child! // => My view
              ),
            )
          ],
        )
      ),
    );
  }
}

My RouterClass :

abstract class RouterClass{

  static Route<dynamic> generate(RouteSettings settings){
    final args = settings.arguments;

    switch(settings.name){

      case RouterName.kHome:
        return MaterialPageRoute(
            builder: (context) => HomeView()
        );

      case RouterName.kProfil:
        return MaterialPageRoute(
            builder: (context) => ProfilView(title: "Profil",)
        );

      default:
        return MaterialPageRoute(
            builder: (context) => Error404View(title: "Erreur")
        );
    }
  }
}

How to do :

  • To pushNamed or open a modal with a button from my sidebar because I have an error
The following assertion was thrown while handling a gesture:
I/flutter (28519): Navigator operation requested with a context that does not include a Navigator.
I/flutter (28519): The context used to push or pop routes from the Navigator must be that of a widget that is a
I/flutter (28519): descendant of a Navigator widget.
  • To hide the sidebar when I want like SplashScreen for example

Any guidance on the best way to accomplish this would be appreciated.

2 Answers

You can use a NavigatorObserver to listen to the changes in the route.

class MyNavObserver with NavigatorObserver {
  final StreamController<int> streamController;

  MyNavObserver({required this.streamController});

  @override
  void didPop(Route route, Route? previousRoute) {
    if (previousRoute != null) {
      if (previousRoute.settings.name == null) {
        streamController.add(3);
      } else {
        streamController
            .add(int.parse(previousRoute.settings.name!.split('/').last));
      }
    }
  }

  @override
  void didPush(Route route, Route? previousRoute) {
    if (route.settings.name == null) {
      streamController.add(3);
    } else {
      streamController.add(int.parse(route.settings.name!.split('/').last));
    }
  }
}

and using StreamController you can make changes to your SidebarTemplate by putting it inside StreamBuilder. This will take care of all the requirements you have mentioned in the question.

Check out the live example here.

As you can see from the Profil screenshot, the sidebar is not part of the widget subtree of the Navigator (the back button is only on the profil widget). This means that you cannot find the Navigator from the context of the sidebar. That is happening because you are using builder in your MaterialApp which inserts widgets above the navigator.

That is also the reason why you cannot hide the sidebar when you want to show a splash screen.

Do you really need to use the builder on MaterialApp? Then you can save the Navigator globally and access it from the sidebar. This is the first article when I search on DuckDuckGo, that you can follow.

To show a SplashScreen you would need to add a state to AppView and change the builder function. Not very nice if you ask me.

I suggest you to re-think your architecture and get rid of the builder in the MaterialApp.

Related