'Flutter - Page with SingleTickerProviderStateMixin cause unnecessary build

I am having this issue github link. What happens is if a widget uses TickerProviderStateMixin then it gets rebuilt when a page navigation occurs. I have a very complex page and rebuilding the whole page causes a UI jank on page navigation. If I do not rebuild then everything is fine no janks. Is there a workaround for this? It seems to me that this is some sort of an internal flutter bug or unexpected behaviour?

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: PageA(title: 'Flutter Demo Home Page'),
    );
  }
}

class PageA extends StatefulWidget {
  PageA({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _PageAState createState() => _PageAState();
}

class _PageAState extends State<PageA>
    with SingleTickerProviderStateMixin {

  TabController tabController;

  @override
  void initState() {
    super.initState();
    tabController = TabController(length: 2, vsync: this);
  }

  void toPageB() {
    //tabController.animateTo(1);
    Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
      return PageB();
    }));
  }

  @override
  Widget build(BuildContext context) {
    print("Page A");
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: TabBar(
          tabs: [
            Text(
              "Tab A",
              style: Theme.of(context).textTheme.bodyText1,
            ),
            Text(
              "Tab B",
              style: Theme.of(context).textTheme.bodyText1,
            )
          ],
          controller: tabController,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: toPageB,
        child: Icon(Icons.add),
      ),
    );
  }
}

class PageB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("Page B");
    return Scaffold(
        appBar: AppBar(
          title: Text("Page A"),
        ),
        body: Container(
          child: Center(
            child: Text("Page A"),
          ),
        ));
  }
}


Solution 1:[1]

@override
  // ignore: must_call_super
  void didChangeDependencies() {}

just add the code to prevent the rebuild, I dont know the side effect, but this walk around works for my app.

Solution 2:[2]

This is the solution I used before.

Change your Page A like

class PageA extends StatefulWidget {
  PageA({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _PageAState createState() => _PageAState();
}

class _PageAState extends State<PageA> {
  TabController tabController;

  // @override
  // void initState() {
  //   super.initState();
  //   tabController = TabController(length: 2, vsync: this);
  // }

  void toPageB() {
    tabController.animateTo(1);
    Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
      return PageB();
    }));
  }

  @override
  Widget build(BuildContext context) {
    print("Page A");
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: CustomTabBar(
          tabs: [
            Text(
              "Tab A",
              style: Theme.of(context).textTheme.bodyText1,
            ),
            Text(
              "Tab B",
              style: Theme.of(context).textTheme.bodyText1,
            )
          ],
          controller: (controller) {
            tabController = controller;
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: toPageB,
        child: Icon(Icons.add),
      ),
    );
  }
}

And add a new class CustomTabBar

class CustomTabBar extends StatefulWidget {
  const CustomTabBar({
    this.controller,
    this.tabs,
    Key? key,
  }) : super(key: key);
  final Function(TabController)? controller;
  final List<Widget>? tabs;

  @override
  _CustomTabBarState createState() => _CustomTabBarState();
}

class _CustomTabBarState extends State<CustomTabBar>
    with SingleTickerProviderStateMixin {
  late TabController tabController;

  @override
  void initState() {
    super.initState();
    tabController =
        TabController(length: widget.tabs?.length ?? 0, vsync: this);
    if (widget.controller != null) {
      widget.controller!(tabController);
    }
  }

  @override
  void dispose() {
    tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TabBar(
      tabs: widget.tabs ?? [],
      controller: tabController,
    );
  }
}

It should fix the issue that Page A rebuild

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 noname.cs
Solution 2 Adnan