1

Here is example of test app: dartpad

Inside app we have 2 main routes: ListPage and ListItemViewScreen. ListItemViewScreen is child route from ListPage. In this case, ShellRoute used only for example

Problem: when we click on any item in list(row 81 - push), ListPage rebuild and _loadData() function is called. And when we go back from ListItemViewScreen, ListPage rebuild too.

How to prevent this unnecessary rebuild?

smie
  • 592
  • 1
  • 9
  • 25
  • if you want to retain the page dont used goNamed but PushNamed and also it can be pop so the back tree of the current page wont rebuild again and again – Arbiter Chil Mar 03 '23 at 10:36

1 Answers1

2

This happens because you actually call the function _loadData() within the build method. This is generally a bad practice.

Instead, convert the widget into a StatefulWidget, set up a member that will hold the future, assign value to it in initState, and use this member in the FutureBuilder as future (see _loadDataFuture):

class ListPage extends StatefulWidget {
  final String type;
  const ListPage({Key? key, required this.type}) : super(key: key);

  @override
  State<ListPage> createState() => _ListPageState();
}

class _ListPageState extends State<ListPage> {
  late final Future<int> _loadDataFuture;
  
  Future<int> _loadData() {
    print("Emulate load data from DB: ${widget.type}");
    return Future.value(1);
  }
  
  @override
  void initState() {
    super.initState();
    _loadDataFuture = _loadData();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: _loadDataFuture,
      builder: (context, snapshot) => ListView.builder(
        itemCount: 100,
        itemBuilder: (context, index) {
          return InkWell(
            onTap: () {
              context.push("/list/${widget.type}/view/$index");
            },
            child: Padding(
              padding: const EdgeInsets.all(20),
              child: Text("item_$index"),
            ),
          );
        },
      ),
    );
  }
}

It is possible that at some point you do want to re-execute the future after it is completed. In the case, remove the final from the declaration of _loadDataFutre, and if you'd like to trigger a reload, use:

setState(() {
  _loadDataFuture = _loadData();
});
Peter Koltai
  • 8,296
  • 2
  • 10
  • 20
  • Thanks! I'm just curious, why after return(go back), widget rebuild. How I understand, ListPage in pages stack, and when we return, it already have state and everything what it need. – smie Mar 03 '23 at 11:29
  • You can't really know and control how often widgets are rebuilt by Flutter engine. Every time the engine thinks it is necessary, it will call `build`. – Peter Koltai Mar 03 '23 at 11:31
  • For example if you have a widget which calls `MediaQuery.of(context)` will be rebuilt a dozen times whenever the soft keypad is opening with an animation. – Peter Koltai Mar 03 '23 at 11:32
  • So you need to avoid every call in a `build` method that does not need to run every time. In some cases `build` method can be called many times within a second! – Peter Koltai Mar 03 '23 at 11:34
  • In real app, I use for this future function from provider. Is this method is normal? ``` @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { _loadDBFuture = context.read().loadDB(widget.status); }); } ``` – smie Mar 03 '23 at 11:37
  • or maybe there is best approach? – smie Mar 03 '23 at 11:38
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/252273/discussion-between-smie-and-peter-koltai). – smie Mar 03 '23 at 11:46