DEV Community

Cover image for Flutter app, layout widget and bottom navigation bar with auto-router
Saad Alkentar
Saad Alkentar

Posted on

Flutter app, layout widget and bottom navigation bar with auto-router

What to expect from this tutorial?

This is a simple tutorial that covers creating a bottom navigation bar with the auto_route library

auto_route is one of the most common Flutter navigation packages, it allows for strongly typed arguments passing, and effortless deep-linking and it uses code generation to simplify route setup.

bottom navigation bar is very popular in modern apps due to its simplicity and wide acceptance from users. this article will cover implementing it in our Flutter app.

The complete picture!

We want to build a bottom navigation bar with three navigation buttons. The screen code should be independent of the navigation code. So, we will start by creating our three screens, just as empty screens, and then connect them through the navigation bar as planned. let's also allow child screens to hide the bottom navigation bar as most apps do.
We have already initiated the auto_route library in previous article, refer to Screen routing with auto_route paragraph in the tutorial for basic library initiation.

Create navigation screens

Let's start by creating three screens; we will call them home, memories, and profile screens.
We will use our previously designed home and memories screens. simplest profile screen would look somewhat like

import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';

@RoutePage()
class ProfileScreen extends StatelessWidget {
  const ProfileScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text("profile"),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

lib/presentation/screens/profile/profile_screen.dart

PS. We can use similarly simple home and memories screens too

notice that we are using RoutePage annotation for all screens, so make sure to generate the router code after creating screens by issuing

dart run build_runner build - delete-conflicting-outputs
Enter fullscreen mode Exit fullscreen mode

next step is to create the bottom navigation screen itself, there are multiple ways to do so as auto_route documentation mentioned, but I found the easiest way is to use AutoTabsScaffold

import 'package:alive_diary_app/config/router/app_router.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';


@RoutePage()
class HomeNavScreen extends StatelessWidget {
  const HomeNavScreen({super.key});

  @override
  Widget build(BuildContext context) {

    return AutoTabsScaffold(
      routes: [
        HomeRoute(),
        const MemoriesRoute(),
        const ProfileRoute(),
      ],

      bottomNavigationBuilder: (context, tabsRouter) {
        return BottomNavigationBar(
          currentIndex: tabsRouter.activeIndex,
          onTap: tabsRouter.setActiveIndex,
          backgroundColor: Theme.of(context).primaryColor,
          selectedItemColor: Colors.white,
          unselectedItemColor: Colors.white54,
          items: const [
            BottomNavigationBarItem(icon: Icon(Icons.home), label: "home"),
            BottomNavigationBarItem(icon: Icon(Icons.menu_book_sharp), label: "memories"),
            BottomNavigationBarItem(icon: Icon(Icons.person), label: "profile"),
          ],
        );
      },
    );

  }
}
Enter fullscreen mode Exit fullscreen mode

lib/presentation/screens/home_nav/home_nav_screen.dart

the two main parameters for AutoTabsScaffold are routes, which indicate the child screen routes, and bottomNavigationBuilder, which builds the bottom navigation bar. we can also build the top navigation bar using appBarBuilder, but I recommend customizing it per screen (each screen has its own bar).

Navigation config

after building the navigation screen itself, it is time to config it in the app_router file

import 'package:alive_diary_app/domain/repositories/preferences_repository.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';

import '../../presentation/screens/home/home_screen.dart';
import '../../presentation/screens/home_nav/home_nav_screen.dart';
import '../../presentation/screens/login/login_screen.dart';
import '../../presentation/screens/memories/memories_screen.dart';
import '../../presentation/screens/profile/profile_screen.dart';
import '../dependencies.dart';


part 'app_router.gr.dart';


@AutoRouterConfig()
class AppRouter extends _$AppRouter {

  bool invalidToken() => locator<PreferencesRepository>().invalidToken();

  @override
  List<AutoRoute> get routes => [
    AutoRoute(page: LoginRoute.page, initial: invalidToken()),

    AutoRoute(
      page: HomeNavRoute.page,
      initial: !invalidToken(),
      children: [
        AutoRoute(page: HomeRoute.page),
        AutoRoute(page: MemoriesRoute.page),
        AutoRoute(page: ProfileRoute.page),
      ],
    ),

  ];
}

final appRouter = AppRouter();
Enter fullscreen mode Exit fullscreen mode

lib/config/router/app_router.dart

this is the complete app router config file, notice how we set the tab screens as children of the main navigation screen. we can also pass the screen title to the AutoRoute if preferable.

We have already explained the token validation previously. in case of an invalid token, we are showing the login screen, otherwise, we are showing the main screen.

great, let's run the app now

home memories profile
Home screen memories screen profile screen

home and memories screens look great, since we have already implemented their own top navigation bar, but what about our new simple profile screen then??

I would recommend creating a layout widget that can be easily reused for all your apps

Layout widget

How to create a layout widget? it is simple actually, we basically need access to the page title and maybe the floating button only, why keep the headache of the full scaffold every time!

import 'package:flutter/material.dart';

class LayoutWidget extends StatelessWidget {
  final Widget child;
  final String title;
  final Widget? floatingActionButton;
  final List<Widget>? actions;

  const LayoutWidget({
    super.key,
    required this.child,
    this.title="home",
    this.floatingActionButton,
    this.actions,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
        iconTheme: const IconThemeData(
          color: Colors.white,
        ),
        backgroundColor: Theme
            .of(context)
            .colorScheme
            .inversePrimary,
        actions: actions,
      ),
      body: Container(
        child: child,
      ),
      floatingActionButton: floatingActionButton,
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

lib/presentation/widgets/layout_widget.dart

we are wrapping the scaffold in our widget, with the ability to add actions, a floating action button, and the main screen body. Let's use this widget for our newly created profile screen

import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';

import '../../widgets/layout_widget.dart';

@RoutePage()
class ProfileScreen extends StatelessWidget {
  const ProfileScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return LayoutWidget(
      title: "Profile",
      child: Container(
        child: Text("profile"),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

lib/presentation/screens/profile/profile_screen.dart

it should look somewhat like

profile screen

looking great! we should use it for both the home and memories screens too. using it everywhere allows us to change the styles of the app navigation bar from one place.

That is it! great job. we will continue working on the app on the next articles so stay tuned
PS. To avoid repeating some ideas, and since I'm building on the same skeletal project, I might jump over some details, please don't hesitate to ask if you found any issues implementing this tutorial for your app.

as always...
Stay tuned 😎

Top comments (0)