1–2 minutes

to read

Maintaining Flutter page state when using NavigationBar

Flutter’s new Material 3 NavigationBar is the replacement for BottomNavigationBar. When implementing this it’s important in many cases to remember a view’s state so that the user returns to a view as they left it – perhaps the scroll position in a list, or the location of a map view. It’s also often desirable to avoid uneccessary server-side api calls to rebuild a page.

There are a few ways of doing this and some Stack Overflow threads on the topic have misleadingly popular answers such as IndexedStack or AutomaticKeepAliveClientMixin. The “proper” way to achieve this is to use PageStorage. The flutter team provide us with an example of how to use it, but at the time of writing the example uses the older BottomNavigationBar, and there is no transition animation between pages.

I played a little with their example and modified it to use the newer NavigationBar along with a lovely FadeThroughTransition using the PageTransitionSwitcher:

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

/// Flutter code sample for [PageStorage].

void main() => runApp(const PageStorageExampleApp());

class PageStorageExampleApp extends StatelessWidget {
  const PageStorageExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int currentTab = 0;
  final PageStorageBucket _bucket = PageStorageBucket();

  @override
  Widget build(BuildContext context) {
    final ThemeData theme = Theme.of(context);
    return Scaffold(
      appBar: AppBar(
        title: const Text('Persistence Example'),
      ),
      body: PageStorage(
        bucket: _bucket,
        child: PageTransitionSwitcher(
          duration: const Duration(milliseconds: 500),
          transitionBuilder: (Widget child, Animation<double> primaryAnimation,
              Animation<double> secondaryAnimation) {
            return FadeThroughTransition(
              animation: primaryAnimation,
              secondaryAnimation: secondaryAnimation,
              child: child,
            );
          },
          child: <Widget>[
            SingleChildScrollView(
              key: PageStorageKey<String>('pageOne'),
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Column(
                  children: <Widget>[
                    for (int i = 0; i < 30; i++)
                      Card(
                        color: Colors.yellow,
                        child: ListTile(
                          leading: const Icon(Icons.info),
                          title: Text('Item $i'),
                          subtitle: Text('This is item number $i.'),
                        ),
                      ),
                  ],
                ),
              ),
            ),
            SingleChildScrollView(
              key: PageStorageKey<String>('pageTwo'),
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Column(
                  children: <Widget>[
                    for (int i = 0; i < 30; i++)
                      Card(
                        color: Colors.blue,
                        child: ListTile(
                          leading: const Icon(Icons.info),
                          title: Text('Item $i'),
                          subtitle: Text('This is item number $i.'),
                        ),
                      ),
                  ],
                ),
              ),
            ),
          ][currentTab],
        ),
      ),
      bottomNavigationBar: NavigationBar(
        selectedIndex: currentTab,
        onDestinationSelected: (int index) {
          setState(() {
            currentTab = index;
          });
        },
        destinations: const <Widget>[
          NavigationDestination(
            selectedIcon: Icon(Icons.home),
            icon: Icon(Icons.home_outlined),
            label: 'Home',
          ),
          NavigationDestination(
            selectedIcon: Icon(Icons.face),
            icon: Icon(Icons.face_outlined),
            label: 'About',
          ),
        ],
      ),
    );
  }
}

Silicon

Hi I’m Ed, owner of Silicon Gorge Apps, building lovely iOS and Android apps for lovely clients.

I’m based in Bristol, South West England, but can build apps for anyone, anywhere.