Skip to content

AutomaticKeepAliveClientMixin does not work in DefaultTabController With 3 tabs #25807

Closed

Description

@cqhchan

Steps to Reproduce

  1. Use the code Template found here -> https://flutter.io/docs/cookbook/design/tabs
class TabBarDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DefaultTabController(
        length: 3,
        child: Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              tabs: [
                Tab(icon: Icon(Icons.directions_car)),
                Tab(icon: Icon(Icons.directions_transit)),
                Tab(icon: Icon(Icons.directions_bike)),
              ],
            ),
            title: Text('Tabs Demo'),
          ),
          body: TabBarView(
            children: [
              Page1(),
              Page2(),
              Page3(),
            ],
          ),
        ),
      ),
    );
  }
}
  1. Create 3 stateful widget with AutomaticKeepAliveClientMixin set to True and super.build(context) under build before the return statement.

  2. Log the initState() function for each page.
    e.g

class Page1 extends StatefulWidget{
  
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState

    return _Page1State();
  }

class _Page1State extends State<Page1> with AutomaticKeepAliveClientMixin{ 
  @override
    void initState() {
      super.initState();
      print("Page 1 init");
    }
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Text("Page 1");
  }
  @override
  bool get wantKeepAlive => true;

}
  1. When moving from tab 1->2->3 or reverse, it only calls initState() on each Page once as intended.
    When moving from tab 1 -> 3, it calls initState on tab 1 and 2 again.
    When moving from tab 3 -> 1, it initStates again for tab 2 and 3.

AutomaticKeepAliveClientMixin doesn't seem to work when moving across tabs that are not next to each other.

Logs

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel beta, v1.0.0, on Mac OS X 10.14 18A391, locale en-SG)
[✓] Android toolchain - develop for Android devices (Android SDK 28.0.3)
[✓] iOS toolchain - develop for iOS devices (Xcode 10.1)
[✓] Android Studio (version 3.2)
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
[✓] VS Code (version 1.30.1)
[✓] Connected device (1 available)

• No issues found!

Activity

  1. zoechi commented on Dec 27, 2018

    @zoechi
    Contributor

    Complete runnable example

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: TabBarDemo(),
        );
      }
    }
    
    class TabBarDemo extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: DefaultTabController(
            length: 3,
            child: Scaffold(
              appBar: AppBar(
                bottom: TabBar(
                  tabs: [
                    Tab(icon: Icon(Icons.directions_car)),
                    Tab(icon: Icon(Icons.directions_transit)),
                    Tab(icon: Icon(Icons.directions_bike)),
                  ],
                ),
                title: Text('Tabs Demo'),
              ),
              body: TabBarView(
                children: [
                  Page1(),
                  Page2(),
                  Page3(),
                ],
              ),
            ),
          ),
        );
      }
    }
    
    class Page1 extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return _Page1State();
      }
    }
    
    class _Page1State extends State<Page1> with AutomaticKeepAliveClientMixin {
      @override
      void initState() {
        super.initState();
        print("Page 1 init");
      }
    
      @override
      Widget build(BuildContext context) {
        super.build(context);
        return Text("Page 1");
      }
    
      @override
      bool get wantKeepAlive => true;
    }
    
    class Page2 extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return _Page2State();
      }
    }
    
    class _Page2State extends State<Page2> with AutomaticKeepAliveClientMixin {
      @override
      void initState() {
        super.initState();
        print("Page 2 init");
      }
    
      @override
      Widget build(BuildContext context) {
        super.build(context);
        return Text("Page 2");
      }
    
      @override
      bool get wantKeepAlive => true;
    }
    
    class Page3 extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return _Page3State();
      }
    }
    
    class _Page3State extends State<Page3> with AutomaticKeepAliveClientMixin {
      @override
      void initState() {
        super.initState();
        print("Page 3 init");
      }
    
      @override
      Widget build(BuildContext context) {
        super.build(context);
        return Text("Page 3");
      }
    
      @override
      bool get wantKeepAlive => true;
    }
  2. added
    frameworkflutter/packages/flutter repository. See also f: labels.
    on Dec 27, 2018
  3. added this to the milestone on Dec 27, 2018
  4. guoChopper commented on Dec 28, 2018

    @guoChopper
  5. zoechi commented on Dec 28, 2018

    @zoechi
  6. SteveAlexander commented on Dec 28, 2018

    @SteveAlexander

    I have a PR that is on the way to fixing this: #24821

    What needs to happen is that each page needs to be given a unique Key. This is because internally, the TabView switches around the order of its children temporarily.

    @zoechi I'd love to get my PR into a fit state to land. I need a conversation with someone from the Flutter team to agree the right way forward on this though. I'm happy to put in the time to get this bug fixed (it's been cropping up in a number of issues expressing related but distinct symptoms). I've had no engagement on the PR though. How can we move this forward?

  7. zoechi commented on Dec 28, 2018

    @zoechi
    Contributor

    @goderbauer perhaps?

  8. gtupak commented on Feb 22, 2019

    @gtupak

    I am facing the same problem with BottomNavigationBar and others have that issue too. Is it related in any way, or does anyone have a fix?

  9. divan commented on Mar 7, 2019

    @divan

    Using PageView helps as a workaround (found solution here)

  10. gtupak commented on Mar 7, 2019

    @gtupak

    Thanks, I'll try and get back to you

  11. gtupak commented on Mar 8, 2019

    @gtupak

    I tried the PageView approach but it didn't work. However, the IndexStack trick did it for me! :) Thanks

  12. withinoneyear commented on Mar 9, 2019

    @withinoneyear

    IndexedStack works for me too. But anybody knows why it needs to be wrapped in such a widget? or can we make it work without IndexedStack?

  13. self-assigned this
    on Apr 11, 2019
  14. chunhtai commented on Apr 23, 2019

    @chunhtai
    Contributor

    change reverted, will reland once issue is fixed

  15. jazzbpn commented on Jun 21, 2020

    @jazzbpn

    Complete runnable example

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: TabBarDemo(),
        );
      }
    }
    
    class TabBarDemo extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: DefaultTabController(
            length: 3,
            child: Scaffold(
              appBar: AppBar(
                bottom: TabBar(
                  tabs: [
                    Tab(icon: Icon(Icons.directions_car)),
                    Tab(icon: Icon(Icons.directions_transit)),
                    Tab(icon: Icon(Icons.directions_bike)),
                  ],
                ),
                title: Text('Tabs Demo'),
              ),
              body: TabBarView(
                children: [
                  Page1(),
                  Page2(),
                  Page3(),
                ],
              ),
            ),
          ),
        );
      }
    }
    
    class Page1 extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return _Page1State();
      }
    }
    
    class _Page1State extends State<Page1> with AutomaticKeepAliveClientMixin {
      @override
      void initState() {
        super.initState();
        print("Page 1 init");
      }
    
      @override
      Widget build(BuildContext context) {
        super.build(context);
        return Text("Page 1");
      }
    
      @override
      bool get wantKeepAlive => true;
    }
    
    class Page2 extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return _Page2State();
      }
    }
    
    class _Page2State extends State<Page2> with AutomaticKeepAliveClientMixin {
      @override
      void initState() {
        super.initState();
        print("Page 2 init");
      }
    
      @override
      Widget build(BuildContext context) {
        super.build(context);
        return Text("Page 2");
      }
    
      @override
      bool get wantKeepAlive => true;
    }
    
    class Page3 extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return _Page3State();
      }
    }
    
    class _Page3State extends State<Page3> with AutomaticKeepAliveClientMixin {
      @override
      void initState() {
        super.initState();
        print("Page 3 init");
      }
    
      @override
      Widget build(BuildContext context) {
        super.build(context);
        return Text("Page 3");
      }
    
      @override
      bool get wantKeepAlive => true;
    }

    Is it possible to update the widget that is preserved using AutomaticKeepAliveClientMixin in Flutter?

  16. Rutbamir commented on Jul 29, 2020

    @Rutbamir

    Thank you so much. It worked for me!

  17. github-actions commented on Aug 14, 2021

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  18. locked as resolved and limited conversation to collaborators on Aug 14, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

c: API breakBackwards-incompatible API changesframeworkflutter/packages/flutter repository. See also f: labels.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions