Pageview rebuilds first widget of the Navigator

42 views Asked by At

I am trying to use BottomNavigator with Nested Navigation for each tab along with preserving the state. I am using Pageview for the displaying the pages. Each page is a Navigator widget has its own navigation stack. The issue here is - in each Navigator, the first page is always rebuilt but the second page is built only once. To prevent the first page from rebuilding I have used AutomaticKeepAliveClientMixin and it works. But I don't get why only the first widget is getting rebuilt and not the second one in the Navigator when I switch the tabs.

import 'package:flutter/material.dart';
void main() {
  runApp(const BottomNavBarWithPageView());
}
final navKeyBooks = GlobalKey<NavigatorState>();

final navKeyCoffee = GlobalKey<NavigatorState>();

final navKeyMovies = GlobalKey<NavigatorState>();

final navKeys = [navKeyBooks, navKeyCoffee, navKeyMovies];

var buildText = '';

toast(BuildContext context) {
  ScaffoldMessenger.of(context).showSnackBar(SnackBar(
    content: Text(buildText),
    duration: const Duration(milliseconds: 400),
  ));
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Bottom Nav Bar',
      theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
          useMaterial3: false),
      home: const HomePage(),
    );
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late int _selectedIndex;
  late List<Widget> _pages;
  late PageController _pageController;

  @override
  void initState() {
    _selectedIndex = 0;
    _pages = const [BooksNavigator(), CoffeeNavigator(), MovieNavigator()];
    _pageController = PageController(initialPage: _selectedIndex);
    super.initState();
  }

  Future<bool> _systemBackButtonPressed() {
    if (navKeys[_selectedIndex].currentState!.canPop()) {
      navKeys[_selectedIndex]
          .currentState!
          .pop(navKeys[_selectedIndex].currentContext);
      return Future.value(false);
    } else {
      // SystemChannels.platform.invokeMethod<void>('SystemNavigator.pop');
      return Future.value(true);
    }
  }

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () => _systemBackButtonPressed(),
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Bottom Nav Bar PageView'),
          leading: const BackButton(
            color: Colors.black,
          ),
        ),
        body: PageView(
          controller: _pageController,
          //The following parameter is just to prevent
          //the user from swiping to the next page.
          physics: const NeverScrollableScrollPhysics(),
          children: _pages,
        ),
        bottomNavigationBar: BottomNavigationBar(
          items: const [
            BottomNavigationBarItem(
              icon: Icon(Icons.book),
              label: 'Books',
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.coffee),
              label: 'Coffee',
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.movie),
              label: 'Movie',
            ),
          ],
          currentIndex: _selectedIndex,
          onTap: (value) {
            setState(() {
              _selectedIndex = value;
              _pageController.jumpToPage(_selectedIndex);
            });
          },
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Navigator(
      key: navKeyBooks,
      initialRoute: '/',
      onGenerateRoute: (settings) {
        return MaterialPageRoute(
            settings: settings,
            builder: (context) {
              switch (settings.name) {
                case '/':
                  return const Books1();
                case '/2':
                  return const Books2();
                default:
                  print('Books Error');
                  return const Center(child: Text('Books Error'));
              }
            });
      },
    );
  }
}

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

  @override
  State<Books1> createState() => _Books1State();
}

class _Books1State extends State<Books1>
    with AutomaticKeepAliveClientMixin<Books1> {
  int counter = 0;
  @override
  Widget build(BuildContext context) {
    print('Books1 build');
    buildText = 'Books1 build';
    WidgetsBinding.instance.addPostFrameCallback((_) => toast(context));
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text('Books1 - $counter'),
          const SizedBox(
            height: 16,
          ),
          ElevatedButton(
            onPressed: () {
              setState(() {
                counter++;
              });
            },
            child: const Text('Increment'),
          ),
          const SizedBox(
            height: 16,
          ),
          ElevatedButton(
            onPressed: () {
              Navigator.of(context).pushNamed('/2');
            },
            child: const Text('Next Page'),
          )
        ],
      ),
    );
  }

  @override
  bool get wantKeepAlive => true;
}

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

  @override
  Widget build(BuildContext context) {
    print('Books2 build');
    buildText = 'Books2 build';
    WidgetsBinding.instance.addPostFrameCallback((_) => toast(context));
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Text('Books2'),
          const SizedBox(
            height: 16,
          ),
          ElevatedButton(
            onPressed: () {
              Navigator.of(context).pop();
            },
            child: const Text('Back'),
          )
        ],
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Navigator(
      key: navKeyCoffee,
      onGenerateRoute: (settings) {
        return MaterialPageRoute(
            settings: settings,
            builder: (context) {
              switch (settings.name) {
                case '/':
                  return const Coffee1();
                case '/2':
                  return const Coffee2();
                default:
                  return const Center(child: Text('Coffee Error'));
              }
            });
      },
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    print('Coffee1 build');
    buildText = 'Coffee1 build';
    WidgetsBinding.instance.addPostFrameCallback((_) => toast(context));
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Text('Coffee1'),
          const SizedBox(
            height: 16,
          ),
          ElevatedButton(
            onPressed: () {
              Navigator.of(context).pushNamed('/2');
            },
            child: const Text('Next Page'),
          )
        ],
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    print('Coffee2 build');
    buildText = 'Coffee2 build';
    WidgetsBinding.instance.addPostFrameCallback((_) => toast(context));
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Text('Coffee2'),
          const SizedBox(
            height: 16,
          ),
          ElevatedButton(
            onPressed: () {
              Navigator.of(context).pop();
            },
            child: const Text('Back'),
          )
        ],
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Navigator(
      key: navKeyMovies,
      onGenerateRoute: (settings) {
        return MaterialPageRoute(
            settings: settings,
            builder: (context) {
              switch (settings.name) {
                case '/':
                  return const Movie1();
                case '/2':
                  return const Movie2();
                default:
                  return const Center(child: Text('Movie Error'));
              }
            });
      },
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    print('Movie1 build');
    buildText = 'Movie1 build';
    WidgetsBinding.instance.addPostFrameCallback((_) => toast(context));
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Text('Movie1'),
          const SizedBox(
            height: 16,
          ),
          ElevatedButton(
            onPressed: () {
              Navigator.of(context).pushNamed('/2');
            },
            child: const Text('Next Page'),
          )
        ],
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    print('Movie2 build');
    buildText = 'Movie2 build';
    WidgetsBinding.instance.addPostFrameCallback((_) => toast(context));
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Text('Movie2'),
          const SizedBox(
            height: 16,
          ),
          ElevatedButton(
            onPressed: () {
              Navigator.of(context).pop();
            },
            child: const Text('Back'),
          )
        ],
      ),
    );
  }
}


View the issue here

I was expecting that all the pages maintain the state when the tabs are switched.

0

There are 0 answers