Flutter: Child Widget's looses state when parent's sate changes

Issue

I am learning Flutter and struggling with some state management issue.

I have a HomeScreen widget which contains Scaffold and a BottomNavigationBar. To switch pages based on the selected tab in BottomNavigationBar I am using PageView with PageController. Here is how the build method of HomeScreen widget looks:

@override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBodyBehindAppBar: _currentIndex == 2,
      appBar: AppBar(...properties),
      body: PageView(
        controller: _pageController,
        children: _pages,
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[...items],
        currentIndex: _currentIndex,
        onTap: _onItemTapped,  //This changes state _currentIndex and calls the method _pageController.jumpToPage(_currentIndex);
      ),
    );
  }
  • _currentIndex initially is 0.
  • _pages is a list containing 3 widgets.
  • _pageController is simple PageController with initialPage set to 0.

If you notice I am using a property extendBodyBehindAppBar: _currentIndex == 2 which is using _currentIndex state and this is causing issue.
When I tap on the last Tab on the BottomNavigationBar the state the _currentIndex changes to 2 and thus the extendBodyBehindAppBar is set as true which makes entire Scaffold rebuild itself and the state of the PageView is lost.

If comment out the line extendBodyBehindAppBar: _currentIndex == 2, then even if the Scaffold rebuilds the state of the PageView widget is preserved.

As of my understanding the Flutter should keep the state of child Widgets even if the parent rebuilds because the WidgetTree is not changed or re-arranged. I tried using Key on PageView but nothing worked.

Any help is very much appreciated.

Solution

refer to Lucas Salton Cardinali post on medium
you need to use PageStorage to persit the state of the child you need after being destroyed.

here the example retrieved from the same page:

import 'package:flutter/material.dart';

// ...

void main() => runApp(MyApp());

// ...

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final List<Widget> pages = <Widget>[
    ColorBoxPage(
      key: PageStorageKey('pageOne'),
    ),
    ColorBoxPage(
      key: PageStorageKey('pageTwo'),
    )
  ];
  int currentTab = 0;
  final PageStorageBucket _bucket = PageStorageBucket();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Persistence Example"),
      ),
      body: PageStorage(
        child: pages[currentTab],
        bucket: _bucket,
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: currentTab,
        onTap: (int index) {
          setState(() {
            currentTab = index;
          });
        },
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'page 1',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            label: 'page2',
          ),
        ],
      ),
    );
  }
}

class ColorBoxPage extends StatelessWidget {
  ColorBoxPage({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemExtent: 250.0,
      itemBuilder: (context, index) => Container(
        padding: EdgeInsets.all(10.0),
        child: Material(
          color: index % 2 == 0 ? Colors.cyan : Colors.deepOrange,
          child: Center(
            child: Text(index.toString()),
          ),
        ),
      ),
    );
  }
}

Answered By – Taur

Answer Checked By – Willingham (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published.