Bottom App Bar Flutter Tutorial | Build a Bottom Navigation Bar in Flutter

In this tutorial, you will learn how to build a bottom app bar in Flutter, complete with navigation icons and a floating action button. The tutorial will cover the basics of using the BottomAppBar and FloatingActionButton widgets, as well as customizing the appearance and behavior of the bar. 

By the end of this tutorial, you will have a fully functional bottom app bar for your Flutter app.

Bottom app bars provide access to a bottom navigation drawer and up to four actions, including the floating action button.

Code Example with Comments Lines:

import 'package:flutter/material.dart';

class BottomAppBarDemo extends StatefulWidget {
  const BottomAppBarDemo({Key? key}) : super(key: key);

  @override
  _BottomAppBarDemoState createState() => _BottomAppBarDemoState();
}

class _BottomAppBarDemoState extends State<BottomAppBarDemo> {
  int _selectedIndex = 0;

  // Define a list of pages to display when a navigation icon is tapped
  final List<Widget> _pages = [
    const Center(child: Text('Page 1')),
    const Center(child: Text('Page 2')),
    const Center(child: Text('Page 3')),
  ];

  // Define the navigation icons to display in the bottom app bar
  final List<BottomNavigationBarItem> _navItems = [
    BottomNavigationBarItem(
      icon: Icon(Icons.home),
      label: 'Home',
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.search),
      label: 'Search',
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.person),
      label: 'Profile',
    ),
  ];

  // Define the floating action button to display in the bottom app bar
  final Widget _fab = FloatingActionButton(
    onPressed: () {},
    child: const Icon(Icons.add),
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // Set the bottom app bar as the main navigation
      bottomNavigationBar: BottomAppBar(
        // Set the color scheme for the bottom app bar
        color: Colors.white,
        // Set the shape of the bottom app bar
        shape: const CircularNotchedRectangle(),
        // Set the position of the floating action button
        notchMargin: 8.0,
        child: BottomNavigationBar(
          // Set the index of the currently selected navigation icon
          currentIndex: _selectedIndex,
          // Set the color scheme for the navigation icons
          selectedItemColor: Colors.blueAccent,
          unselectedItemColor: Colors.grey,
          // Define the navigation icons
          items: _navItems,
          // Call the _onNavItemTapped function when a navigation icon is tapped
          onTap: _onNavItemTapped,
        ),
      ),
      // Set the body of the app to display the current page
      body: _pages[_selectedIndex],
      // Set the floating action button to display in the bottom app bar
      floatingActionButton: _fab,
      // Set the position of the floating action button
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    );
  }

  // Function called when a navigation icon is tapped
  void _onNavItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }
}

Step by Step Code Explanation:

  • Import the flutter/material.dart package.
  • Create a new StatefulWidget called BottomAppBarDemo.
  • Define an integer _selectedIndex to keep track of the currently selected navigation icon.
  • Define a list of Widgets called _pages to display when a navigation icon is tapped.
  • Define a list of BottomNavigationBarItems called

..

Bottom app bar demo:

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

class BottomAppBarDemo extends StatefulWidget {
  const BottomAppBarDemo({super.key});

  @override
  State createState() => _BottomAppBarDemoState();
}

class _BottomAppBarDemoState extends State<BottomAppBarDemo>
    with RestorationMixin {
  final RestorableBool _showFab = RestorableBool(true);
  final RestorableBool _showNotch = RestorableBool(true);
  final RestorableInt _currentFabLocation = RestorableInt(0);

  @override
  String get restorationId => 'bottom_app_bar_demo';

  @override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    registerForRestoration(_showFab, 'show_fab');
    registerForRestoration(_showNotch, 'show_notch');
    registerForRestoration(_currentFabLocation, 'fab_location');
  }

  @override
  void dispose() {
    // Disposing state variables
    _showFab.dispose();
    _showNotch.dispose();
    _currentFabLocation.dispose();
    super.dispose();
  }

  // Since FloatingActionButtonLocation is not an enum, the index of the
  // selected FloatingActionButtonLocation is used for state restoration.
  static const List<FloatingActionButtonLocation> _fabLocations = [
    FloatingActionButtonLocation.endDocked,
    FloatingActionButtonLocation.centerDocked,
    FloatingActionButtonLocation.endFloat,
    FloatingActionButtonLocation.centerFloat,
  ];

  void _onShowNotchChanged(bool value) {
    setState(() {
      _showNotch.value = value;
    });
  }

  void _onShowFabChanged(bool value) {
    setState(() {
      _showFab.value = value;
    });
  }

  void _onFabLocationChanged(int? value) {
    setState(() {
      _currentFabLocation.value = value!;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        automaticallyImplyLeading: false,
        title: const Text('Bottom app bar'),
      ),
      body: ListView(
        padding: const EdgeInsets.only(bottom: 88),
        children: [
          SwitchListTile(
            title: const Text(
              'Floating action button',
            ),
            value: _showFab.value,
            onChanged: _onShowFabChanged,
          ),
          SwitchListTile(
            title: const Text('Notch'),
            value: _showNotch.value,
            onChanged: _onShowNotchChanged,
          ),
          const Padding(
            padding: EdgeInsets.all(16),
            child: Text('Floating action button position'),
          ),
          RadioListTile<int>(
            title: const Text(
              'Docked - End',
            ),
            value: 0,
            groupValue: _currentFabLocation.value,
            onChanged: _onFabLocationChanged,
          ),
          RadioListTile<int>(
            title: const Text(
              'Docked - Center',
            ),
            value: 1,
            groupValue: _currentFabLocation.value,
            onChanged: _onFabLocationChanged,
          ),
          RadioListTile<int>(
            title: const Text(
              'Floating - End',
            ),
            value: 2,
            groupValue: _currentFabLocation.value,
            onChanged: _onFabLocationChanged,
          ),
          RadioListTile<int>(
            title: const Text(
              'Floating - Center',
            ),
            value: 3,
            groupValue: _currentFabLocation.value,
            onChanged: _onFabLocationChanged,
          ),
        ],
      ),
      floatingActionButton: _showFab.value
          ? Semantics(
        container: true,
        sortKey: const OrdinalSortKey(0),
        child: FloatingActionButton(
          onPressed: () {},
          tooltip: 'Create',
          backgroundColor: Theme.of(context).colorScheme.primary,
          child: Icon(Icons.add, color: Theme.of(context).colorScheme.onPrimary),
        ),
      )
          : null,
      floatingActionButtonLocation: _fabLocations[_currentFabLocation.value],
      bottomNavigationBar: _DemoBottomAppBar(
        fabLocation: _fabLocations[_currentFabLocation.value],
        shape: _showNotch.value ? const CircularNotchedRectangle() : null,
      ),
    );
  }
}

class _DemoBottomAppBar extends StatelessWidget {
  const _DemoBottomAppBar({
    required this.fabLocation,
    this.shape,
  });

  final FloatingActionButtonLocation fabLocation;
  final NotchedShape? shape;

  static final centerLocations = <FloatingActionButtonLocation>[
    FloatingActionButtonLocation.centerDocked,
    FloatingActionButtonLocation.centerFloat,
  ];

  @override
  Widget build(BuildContext context) {
    return Semantics(
      sortKey: const OrdinalSortKey(1),
      container: true,
      label: 'Open navigation menu',
      child: BottomAppBar(
        shape: shape,
        //padding: EdgeInsets.all(8),
        child: IconTheme(
          data: IconThemeData(color: Theme.of(context).colorScheme.onPrimary),
          child: Row(
            children: [
              IconButton(
                tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
                icon: Icon(Icons.menu, color: Theme.of(context).colorScheme.primary),
                onPressed: () {},
              ),
              if (centerLocations.contains(fabLocation)) const Spacer(),
              IconButton(
                tooltip: 'Search',
                icon: Icon(Icons.search, color: Theme.of(context).colorScheme.primary),
                onPressed: () {},
              ),
              IconButton(
                tooltip: 'Favorite',
                icon: Icon(Icons.favorite, color: Theme.of(context).colorScheme.primary),

                onPressed: () {},
              )
            ],
          ),
        ),
      ),
    );
  }
}

  • Import the required dependencies for the Flutter app: flutter/material.dart and flutter/rendering.dart.
  • Define a StatefulWidget called BottomAppBarDemo that extends State.
  • Define the RestorationMixin to provide state restoration support for this widget.
  • Define three Restorable variables to track the state of the widget: _showFab, _showNotch, and _currentFabLocation. Restorable variables provide a way to restore state between app sessions.
  • Implement the restorationId getter to specify the restoration ID for this widget.
  • Implement the restoreState method to register the Restorable variables for restoration.
  • Implement the dispose method to dispose of the Restorable variables.
  • Define a list of FloatingActionButtonLocation called _fabLocations that contains four different options for the location of the floating action button.
  • Define three callback methods: _onShowNotchChanged, _onShowFabChanged, and _onFabLocationChanged that update the state of _showNotch, _showFab, and _currentFabLocation respectively.
  • Implement the build method that returns a Scaffold widget that contains the app bar, body, floating action button, and bottom app bar.
  • The app bar contains a Text widget with the title "Bottom app bar".
  • The body contains a ListView widget with a SwitchListTile widget to toggle the visibility of the floating action button and another SwitchListTile widget to toggle the visibility of the notch.
  • The body also contains a Padding widget with a Text widget that reads "Floating action button position", followed by four RadioListTile widgets that allow the user to select the position of the floating action button.
  • The floating action button is conditionally visible based on the value of _showFab and contains an Icon widget that represents the action to be performed when the button is pressed.
  • The floating action button location is determined by the value of _currentFabLocation and is set using the _fabLocations list.
  • The bottom app bar is a custom widget called _DemoBottomAppBar that receives the fabLocation and shape parameters. The fabLocation is used to determine if a Spacer widget is added to the row of icons. The shape parameter is used to specify the shape of the bottom app bar, which is either null or a CircularNotchedRectangle widget.
  • The bottom app bar contains a Row of IconButton widgets that represent actions to be performed when pressed. These actions include opening the navigation menu, searching for content, and adding content to favorites.



..

Comments