Why is Flutter disposing my widget state object in a tabbed interface?
Asked Answered
B

1

11

I have a tabbed Flutter interfaces using DefaultTabController with 3 pages, each a stateful widget. I seem to be able to switch between the first two tabs just fine, but when I tab to the 3rd page the state object for the first page gets disposed. Subsequent state updates (using setState()) then fail.

I've overridden the dispose() method of the state object for the first page so that it prints a message when disposed. It's getting disposed as soon as I hit the third tab. I can't find documentation on why Flutter disposes state objects. (Plenty on lifecycle but not the reasons for progressing through the stages.)

Nothing unusual about setting up the tabs, I don't think.

 @override
  Widget build(BuildContext context) {
    return new MaterialApp(

      home: DefaultTabController(
        length: 3,
        child: Scaffold(
          appBar: AppBar(
            title: Text(title),
            bottom: TabBar(
              tabs: [
                // Use these 3 icons on the tab bar.
                Tab(icon: Icon(Icons.tune)),
                Tab(icon: Icon(Icons.access_time)),
                Tab(icon: Icon(Icons.settings)),
              ],
            ),
          ),
          body: TabBarView(
            children: [
              // These are the 3 pages relating to to the tabs.
              ToolPage(),
              TimerPage(),
              SettingsPage(),
            ],

The pages themselves are pretty simple. No animation. Just switches and sliders, etc. I think about the only departures I've made from the code examples I've seen are that I've made the main app widget stateful, I've extended the tab pages to support wantKeepAlive() and I override wantKeepAlive to set it to true (thought that might help), and I call setState() on the first two tabs from an external object, which Android Studio flags with a weak warning. State on the first two pages gets updated when reading from a websocket this app opens to a remote server. Upon further testing I've noticed it only happens only when I tab from the first to the third page. Going from first to second or second to third does not trigger the dispose.

I would expect the State object associated with a StatefulWidget to stay around and with wantKeepAlive = true don't understand why it's being disposed when I click to the third tab, especially because it doesn't happen when I click to the second or from the second to third.

Blasien answered 25/4, 2019 at 13:41 Comment(0)
C
17

This happens because TabBarView doesn't always show all tabs. Some may be outside of the screen boundaries.

In that case, the default behavior is that Flutter will unmount these widgets to optimize resources.

This behavior can be changed using what Flutter calls "keep alive".

The easiest way is to use AutomaticKeepAliveClientMixin mixin on a State subclass contained inside your tab as such:

class Foo extends StatefulWidget {
  @override
  _FooState createState() => _FooState();
}

class _FooState extends State<Foo> with AutomaticKeepAliveClientMixin {
  @override
  bool wantKeepAlive = true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Container();
  }
}

This tells Flutter that Foo should not be disposed when it leaves the screen.

Cimmerian answered 25/4, 2019 at 13:47 Comment(4)
Thanks Remi. I thought that, too. I have this, but it doesn't seem to work. ```` @override bool get wantKeepAlive => true; ````Blasien
I went back and changed it to override the wantKeepAlive definition instead of the getter (as I had done according to an example I had seen) but it did not change the behavior.Blasien
You need to call super.build() to make this working.Dougie
What do you mean by "State subclass contained inside your tab"?Fidelfidela

© 2022 - 2024 — McMap. All rights reserved.