Skip to content

Pages on Navigator stack rebuild when a new page is pushed #11655

Closed

Description

@cornelius-tan

Steps to Reproduce

Pages on Navigator stack rebuild when a new page is pushed onto the stack.
Suppose the pages were numbered numerically, in the order they are pushed onto the stack i.e. page 1 -> page 2 -> page 3. When we push Page 3, previous pages from page 1 to 2 rebuild as evident from the print statements. Is this a bug?

import 'package:flutter/material.dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(id: 1),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.id}) : super(key: key);

  final int id;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    print('Page ${widget.id} built!');
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.id.toString()),
      ),
      body: new Center(
        child: new RaisedButton(
          child: new Text('Open next page'),
          onPressed: () {
            Navigator.of(context).push(
              new MaterialPageRoute(
                builder: (_) => new MyHomePage(id: widget.id + 1),
                maintainState: true
              )
            );
          }
        )
      ),
    );
  }
}

Logs

When page 2 is pushed, the log outputs

I/flutter (21176): Page 2 built!
I/flutter (21176): Page 2 built!
I/flutter (21176): Page 1 built!

Expected:

I/flutter (21176): Page 2 built!  // only 

When page 3 is pushed, the log outputs

I/flutter (21176): Page 2 built!
I/flutter (21176): Page 3 built!
I/flutter (21176): Page 3 built!
I/flutter (21176): Page 2 built!
I/flutter (21176): Page 1 built!

Expected:

I/flutter (21176): Page 3 built!  // only 

Flutter Doctor

[√] Flutter (on Microsoft Windows [Version 10.0.15063], locale en-US, channel master)
    • Flutter at C:\Users\tzm\Downloads\flutter_sdk
    • Framework revision b156a0f054 (5 days ago), 2017-08-11 22:01:40 -0700
    • Engine revision fef7d827d6
    • Tools Dart version 1.25.0-dev.9.0

[√] Android toolchain - develop for Android devices (Android SDK 25.0.3)
    • Android SDK at C:\Users\tzm\AppData\Local\Android\sdk
    • Platform android-26, build-tools 25.0.3
    • Java binary at: C:\Program Files\Android\Android Studio\jre\bin\java
    • Java version OpenJDK Runtime Environment (build 1.8.0_112-release-b06)

Activity

  1. kerego commented on Nov 30, 2017

    @kerego

    I have noticed this behavior but couldn't figure out what is the problem, can someone from the team comment on this?

  2. Hixie commented on Nov 30, 2017

    @Hixie
    Contributor

    This is working as intended. In general, you should assume that all widgets can rebuild at any time, that they don't is mostly just an optimisation.

    In particular, routes will rebuild because their navigator state has changed so they might need to update how they draw back buttons and the like.

  3. added
    f: routesNavigator, Router, and related APIs.
    on Mar 8, 2018
  4. lidroider commented on Jul 6, 2018

    @lidroider

    @Hixie
    I have same issue with @cornelius-tan
    I understand that when navigator state has changed, routes will rebuild
    But I follow the document of MaterialPageRoute, I found out maintainState = true to keep state of previous route.
    So what is the "actual" meaning of maintainState and what is the state in maintainState?
    Please give me your opinion
    p/s: sorry for my English

  5. Hixie commented on Jul 13, 2018

    @Hixie
    Contributor

    maintainState controls whether the route's widgets and render objects are kept around or not. If you set it to false on a route, then when another route is pushed on top, the entire subtree is thrown away until that route is popped.

  6. added
    frameworkflutter/packages/flutter repository. See also f: labels.
    on Jul 22, 2018
  7. lidroider commented on Aug 8, 2018

    @lidroider

    @Hixie
    Is there any way to prevent rebuild a widget when another route is pushed on top?
    Ex: I have route /a -> Widget A, /b -> Widget B. When I push route /b on /a, Widget A is rebuilt and Widget B is built; when i pop /b, Widget A is rebuilt again.
    I expect Widget A not to be rebuilt when i push and pop /b
    Please help me
    Thks

  8. Hixie commented on Aug 13, 2018

    @Hixie
    Contributor

    You should generally assume that every widget will be rebuild every frame, and design your build methods to be idempotent (i.e. running them multiple times should do nothing different than running them once), and fast (so that there's no problem with running them a lot). In practice we run build methods less often than that but you shouldn't rely on that.

  9. shivamlko commented on Sep 21, 2018

    @shivamlko

    @Hixie I have FutureBuilder and List to populate in Page 1, when I open Page 2, FutureBuilder runs again and duplicates data, and if I pop Page 2 , then again FutureBuilder runs, hence 3 times duplicate data. i am stuck in this
    How to maintain and best way to do not execute any FutureBuilder or StreamBuilder when I call setState()
    for now I have to make bool var to stop each widget duplicate data ,I this code of Pagination works but when I call but duplicate when pop from Page 2 or setState() is called

    import 'package:flutter/material.dart';
    import '../widgets/imageGridItem.dart';
    import '../widgets/imageBigGridItem.dart';
    import 'package:graphql_flutter/graphql_flutter.dart';
    import '../utils/constants.dart' as constants;
    import '../temp/queries.dart';
    import 'package:http/http.dart' as http;
    import '../blocs/homebloc.dart';
    import 'dart:io';
    import 'dart:async';
    import 'dart:convert';
    
    class GridListPageState extends State<GridListPage> {
      String endCursorID;
      bool isRefresh = false;
      bool firstTime = true;
    
    
      String lastCursor;
      ScrollController controller;
      List listData = new List();
    
      @override
      void initState() {
        controller = new ScrollController()..addListener(_scrollListener);
        super.initState();
      }
    
      @override
      void dispose() {
        controller.removeListener(_scrollListener);
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
    
        print("build");
        if(firstTime) {
          firstTime=false;
          loadMoreData();
        }
        return StreamBuilder(
            stream: bloc.listData,
            builder: (context, snapshot) {
              print('Future $snapshot ');
              if (snapshot.error != null) {
                return Text(snapshot.error.toString());
              }
    
          
              isRefresh = false;
              if (snapshot.hasData) {
                print(snapshot.data);
                listData.addAll(snapshot.data);
    
                return dataBody(context);
              }
              if (snapshot.connectionState == ConnectionState.waiting)
                return loadingView();
            }
    
    
            );
      }
    
      Widget dataBody(BuildContext context2) {
        print('Size ${listData.length}');
        return Container(
            color: constants.backgroundColor,
            child: ListView.builder(
                controller: controller,
                itemCount: listData.length == 0 ? 0 : listData.length,
                itemBuilder: (BuildContext context, int index) {
                  print('1244');
              
                  return addNewItem(listData[index]);
                }));
      }
    
      Widget addNewItem(var data) {
        print(data['element']['node']['id']);
        return ImageBigGridItem(data);
      }
    
      void _scrollListener() {
        print('pagination  ');
        if (controller.position.extentAfter < 200) {
        
          if (!isRefresh) {
          
            print('pagin $lastCursor');
            isRefresh = true;
            loadMoreData().then((list) {
           
              setState(() {
                isRefresh = false;
           
              });
            });
          }
        }
      }
    
      Widget loadingView() {
        return Center(
            child: CircularProgressIndicator(
          backgroundColor: Colors.green,
        ));
      }
    
     
      Future<List> loadMoreData() async {
        isRefresh = true;
    
    
        String body;
        body = json.encode({"query": "{tagview(cursor: $lastCursor)}"});
        print("Outside  $lastCursor $body");
    
        var url = Uri.parse(
            'https://ueeu1geb42.execute-api.ap-northeast-1.amazonaws.com/staging');
        http.Response response = await http.post(url,
            headers: {
              HttpHeaders.AUTHORIZATION: "Bearer ${constants.apiToken}",
              HttpHeaders.CONTENT_TYPE: "application/json"
            },
            body: body);
    
        if (response.statusCode == 200) {
          Map data = json.decode(response.body);
          print('JSON $data');
       
          List list = data['data']['tagview']['edges'];
          lastCursor = data['data']['tagview']['pageInfo']['endCursor'];
          print('###$list');
          lastCursor = "\"$lastCursor\"";
          print('End Cursor $lastCursor');
          bloc.addtoList(data['data']['tagview']['edges']);
          bloc.listen();
         
        }
     
      }
    
     
    }
    
    class GridListPage extends StatefulWidget {
      GridListPage() : super();
    
      @override
      GridListPageState createState() => new GridListPageState();
    }
  10. zoechi commented on Sep 24, 2018

    @zoechi
    Contributor

    @shivamlko you'll need to restructure your code so that the data is not fetched again. For example you can cache it and only fetch from the server if the cached value is null

  11. added
    customer: crowdAffects or could affect many people, though not necessarily a specific customer.
    on Dec 29, 2018
  12. and1990 commented on Jan 13, 2019

    @and1990

    wow. The way widget builds is too complicated for the programmer.

  13. rrousselGit commented on Mar 4, 2019

    @rrousselGit
    Contributor

    I'll reference it here because this is a very common question:

    This is intended. If this causes any problem for you, then you're likely using widgets incorrectly.

    You may want to read https://stackoverflow.com/questions/52249578/how-to-deal-with-unwanted-widget-build, which showcase one of the common pitfall and how to solve it.

  14. cosinus84 commented on Mar 27, 2019

    @cosinus84

    Because of this behaviour when you want to show the status bar when you push A->B: the status bar appears and dissapears again because widget A is rebuild again after widget B build. Widget A hides the status bar, and B has the status bar.

  15. Afsar-Pasha commented on Apr 13, 2019

    @Afsar-Pasha

    Any progress?

  16. 103 remaining items

  17. rrousselGit commented on Nov 29, 2019

    @rrousselGit
    Contributor

    On a related note @Hixie, do you think it'd be feasible to have a "true" fix to this rebuild by changing Overlay's _Theatre call from:

    _Theatre(
      onstage: Stack(
        fit: StackFit.expand,
        children: onstageChildren.reversed.toList(growable: false),
      ),
      offstage: offstageChildren,
    );

    to:

    _Theatre(
      onstage: onstageChildren.reversed.toList(growable: false),
      offstage: offstageChildren,
    );

    where the previous Stack would be implemented manually by TheatreRender instead of using composition?

    From my understanding, it is the reparenting from onstage to offstage that is the original problem this issue was about.

    If that's a reasonable fix, I might give it a try and implement it myself.

  18. Hixie commented on Nov 29, 2019

    @Hixie
    Contributor

    Please file a new bug to discuss specific proposals.

  19. TreeLiked commented on Dec 8, 2019

    @TreeLiked

    How do I solve this problem? There are many images on my homepage. When I navigate to a new page or return, the images are reloaded, causing the page to flicker.

  20. cuong1112035 commented on Dec 8, 2019

    @cuong1112035

    How do I solve this problem? There are many images on my homepage. When I navigate to a new page or return, the images are reloaded, causing the page to flicker.

    You can use const to to create your images. They should not reload when their parent reload

  21. TreeLiked commented on Dec 8, 2019

    @TreeLiked

    @cuong1112035 Oh, however, the const constructor requires constants during compilation. The picture here is a network picture, which is a link requested from the server. I cannot use the const constructor.

  22. cuong1112035 commented on Dec 8, 2019

    @cuong1112035

    @cuong1112035 Oh, however, the const constructor requires constants during compilation. The picture here is a network picture, which is a link requested from the server. I cannot use the const constructor.

    you could try AsyncMemoizer to load your remote images just one time. Or maybe just wrap your remote image within a const widget. Hope this help

  23. TreeLiked commented on Dec 8, 2019

    @TreeLiked

    @cuong1112035 Sorry, can you give me an example about using the image const constructor? I have been delaying this issue for a long time, thank you very much~ I am using CachedNetWorkImage

  24. tony123S commented on Dec 8, 2019

    @tony123S

    @TreeLiked Are you using bloc?

  25. TreeLiked commented on Dec 8, 2019

    @TreeLiked

    @TreeLiked Are you using bloc?
    No, the homepage like the picture 'here', the image container is cachednetworkimage , when i push a new route, this page rebuild and rebuild when route pop, the images will rebuild and page flicker not smooth, is there a good solution for this , thanks~

  26. cuong1112035 commented on Dec 8, 2019

    @cuong1112035

    @cuong1112035 Sorry, can you give me an example about using the image const constructor? I have been delaying this issue for a long time, thank you very much~ I am using CachedNetWorkImage

    "const constructor requires constants during compilation" you are right. I just check it and there are no ways to use const to prevent your image widget to rebuild.
    Also, CachedNetWorkImage caches your images already so that you don't need to use AsyncMemoizer for caching.
    Sorry for my stupid suggestion, hope this issue will be resolved in next Flutter versions

  27. TreeLiked commented on Dec 9, 2019

    @TreeLiked

    @cuong1112035 Sorry, can you give me an example about using the image const constructor? I have been delaying this issue for a long time, thank you very much~ I am using CachedNetWorkImage

    "const constructor requires constants during compilation" you are right. I just check it and there are no ways to use const to prevent your image widget to rebuild.
    Also, CachedNetWorkImage caches your images already so that you don't need to use AsyncMemoizer for caching.
    Sorry for my stupid suggestion, hope this issue will be resolved in next Flutter versions

    oh, yes, thank you still~

  28. SBDavid commented on Dec 9, 2019

    @SBDavid

    This is working as intended. In general, you should assume that all widgets can rebuild at any time, that they don't is mostly just an optimisation.

    In particular, routes will rebuild because their navigator state has changed so they might need to update how they draw back buttons and the like.

    I fix the "rebuild" problem by adding a key to Overlay build function.

    First I add a GlobalKey to OverlayEntry class. For example _key2. This key will be used in TickerMode, the purpose is to give a Key to overlay offstage elements.

    class OverlayEntry {
    
      OverlayState _overlay;
      final GlobalKey<_OverlayEntryState> _key = GlobalKey<_OverlayEntryState>();
    
      final GlobalKey _key2 = GlobalKey();
    }

    And the next step is add _key2 to build function.

    class OverlayState extends State<Overlay> with TickerProviderStateMixin {
    @override
      Widget build(BuildContext context) {
        final List<Widget> onstageChildren = <Widget>[];
        final List<Widget> offstageChildren = <Widget>[];
        bool onstage = true;
        print("overlay build");
        for (int i = _entries.length - 1; i >= 0; i -= 1) {
    
          final OverlayEntry entry = _entries[i];
          if (onstage) {
            print("onstage ${entry._key}");
            onstageChildren.add(_OverlayEntry(entry));
            if (entry.opaque)
              onstage = false;
          } else if (entry.maintainState) {
            offstageChildren.add(TickerMode(key: entry._key2, enabled: false, child: _OverlayEntry(entry)));
            print("offstage ${entry._key}");
          } else {
            print("!maintainState ${entry._key}");
          }
        }
        return _Theatre(
          onstage: Stack(
            fit: StackFit.expand,
            children: onstageChildren.reversed.toList(growable: false),
          ),
          offstage: offstageChildren,
        );
      }
    
    }

    And then Flutter diff algorithm will know how to update offstage element properly, other than just inflateWidget. The inflateWidget method will reload images which were used in offstage elements, thus cause serious performance issue.

  29. Hixie commented on Dec 10, 2019

    @Hixie
    Contributor

    Please file a new bug to discuss specific proposals.

  30. locked and limited conversation to collaborators on Dec 10, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    c: performanceRelates to speed or footprint issues (see "perf:" labels)customer: crowdAffects or could affect many people, though not necessarily a specific customer.f: routesNavigator, Router, and related APIs.frameworkflutter/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