Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

StatefulWidget rebuilding unnecessarily after Navigator.push()/pop() #18366

Closed
cfkloss opened this issue Jun 11, 2018 · 20 comments
Closed

StatefulWidget rebuilding unnecessarily after Navigator.push()/pop() #18366

cfkloss opened this issue Jun 11, 2018 · 20 comments
Labels
f: routes Navigator, Router, and related APIs. framework flutter/packages/flutter repository. See also f: labels.

Comments

@cfkloss
Copy link

cfkloss commented Jun 11, 2018

In the example bellow, every time we call Navigator.push()/Navigator.pop() the widget is unnecessarily
rebuilding.

It only happens with a StatefulWidget. If we use a StatelessWidget the rebuild does not occurs.

This behavior is harmless with simple UI pages but can impact negatively when we have the widget body wrapped around by a FutureBuilder doing some expensive computation.

Steps to Reproduce

  1. Run the code below.
  2. Click 'Navigate to page 2' to navigate to the Page 2.
  3. The console shows every time the widget rebuilds

Code

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Page1(),
    );
  }
}

class Page1 extends StatefulWidget {
  @override
  Page1State createState() {
    return new Page1State();
  }
}

class Page1State extends State<Page1> {
  var i = 0;

  @override
  Widget build(BuildContext context) {
    i++;
    print("rebuild $i");
    return Scaffold(appBar: AppBar(title: Text('Page1')),
        body: Center(
            child: Column(mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: <Widget>[
                  Text('Page1 build counter: $i'),
                  RaisedButton(
                    child: Text('Navigate to page 2'),
                    onPressed: () {
                      Navigator.push(context,
                          MaterialPageRoute(builder: (_) {
                            print('inside MaterialPageRoute builder');
                            return Scaffold(
                                appBar: AppBar(title: Text('Page2')),
                                body: Center(child: Text('$i')));
                          })
                      );
                    },
                  )
                ])
        ));
  }
}

Logs

analyze.txt
doctor.txt
run.txt

@cfkloss
Copy link
Author

cfkloss commented Jun 11, 2018

I think it is similar to #11426

@blaneyneil
Copy link

yeah, the same core issue as #11426 but this description is more helpful - it has nothing to do with tabs per se. in addition to the above mentioned problem of needlessly re-fetching and/or re-computing, you can't maintain a list's scroll position as described by those docs. if you're 100 cards deep, push to a card detail, and then pop, you're at the top of the list again.

for those facing that problem, don't call ScrollController from InitState. instead, call it inside the build:

ScrollController _scrollController;
  double _offset = 0.0;

  @override
  Widget build(BuildContext context) {

    _scrollController = new ScrollController(initialScrollOffset: _offset);

and then inside your stream/future builder:

return new ListView.builder(
                controller: _scrollController,
                itemCount: docs.length,
                itemExtent: 150.0,
                itemBuilder: (BuildContext context, int index) {

                  if (_scrollController.offset > 100) { // 100 is arbitrary
                    _offset = _scrollController.offset;
                  }
                  return new SomeCard(doc: docs);
                },
              );


@zoechi zoechi added framework flutter/packages/flutter repository. See also f: labels. f: routes Navigator, Router, and related APIs. labels Sep 25, 2018
@zoechi zoechi added this to the Goals milestone Sep 25, 2018
@lishuhao
Copy link

lishuhao commented Oct 7, 2018

I found flutter_gallery has the same problem

@zoechi
Copy link
Contributor

zoechi commented Oct 7, 2018

has the same problem

it's basically working as intended. You should assume that build() is called multiple times at various times.

https://flutter.io/search/?cx=007067728241810524621%3Agm6vraqlc8c&ie=UTF-8&hl=en&q=maintainstate might help to avoid build being called when a new route is added.

@lishuhao
Copy link

But if a page has many images ,you can see that image blink as if render again. I use image cache
solve this. sorry for my poor english!

@zoechi
Copy link
Contributor

zoechi commented Oct 16, 2018

@lishuhao what's the problem with using image cache?

@lishuhao
Copy link

check this video

@lishuhao
Copy link

Focus on the picture of the first page

@amoy1110
Copy link

yeah, the same core issue as #11426 but this description is more helpful - it has nothing to do with tabs per se. in addition to the above mentioned problem of needlessly re-fetching and/or re-computing, you can't maintain a list's scroll position as described by those docs. if you're 100 cards deep, push to a card detail, and then pop, you're at the top of the list again.

for those facing that problem, don't call ScrollController from InitState. instead, call it inside the build:

ScrollController _scrollController;
  double _offset = 0.0;

  @override
  Widget build(BuildContext context) {

    _scrollController = new ScrollController(initialScrollOffset: _offset);

and then inside your stream/future builder:

return new ListView.builder(
                controller: _scrollController,
                itemCount: docs.length,
                itemExtent: 150.0,
                itemBuilder: (BuildContext context, int index) {

                  if (_scrollController.offset > 100) { // 100 is arbitrary
                    _offset = _scrollController.offset;
                  }
                  return new SomeCard(doc: docs);
                },
              );

Thanks this really helped me save the scroll position of a long list when navigating to another screen and backing out of it.
For my sepcific issue, I found it to be exactly precise when you place the _offset = _scrollController.offset; inside the onTap of the ListTile or right before the call to Navigator.push().

@goderbauer
Copy link
Member

This is another dupe of #11655.

@LoverJoker
Copy link

@zoechi Hello, The home page has BottomNavigationBar, there are three sub-pages, now enter the second-level page(user Navigator.push()) from the third page. After returning back, the page becomes the first sub-page, not the original third page. How can I solve this problem? Please forgive my English

@ManojMM026
Copy link

yeah, the same core issue as #11426 but this description is more helpful - it has nothing to do with tabs per se. in addition to the above mentioned problem of needlessly re-fetching and/or re-computing, you can't maintain a list's scroll position as described by those docs. if you're 100 cards deep, push to a card detail, and then pop, you're at the top of the list again.

for those facing that problem, don't call ScrollController from InitState. instead, call it inside the build:

ScrollController _scrollController;
  double _offset = 0.0;

  @override
  Widget build(BuildContext context) {

    _scrollController = new ScrollController(initialScrollOffset: _offset);

and then inside your stream/future builder:

return new ListView.builder(
                controller: _scrollController,
                itemCount: docs.length,
                itemExtent: 150.0,
                itemBuilder: (BuildContext context, int index) {

                  if (_scrollController.offset > 100) { // 100 is arbitrary
                    _offset = _scrollController.offset;
                  }
                  return new SomeCard(doc: docs);
                },
              );

Did not help me.
Whenever I do Navigator.push My current screens initState gets called again. which rests my scrolling position in list. When I come back from detail view it again calls initState and everything again. Which takes me back to 0th position in listivew.

I am having listview with multiple child listivew in it who has stream builder which gets the data from frirestore. Please help me with this.

@anisalibegic
Copy link
Contributor

yeah, the same core issue as #11426 but this description is more helpful - it has nothing to do with tabs per se. in addition to the above mentioned problem of needlessly re-fetching and/or re-computing, you can't maintain a list's scroll position as described by those docs. if you're 100 cards deep, push to a card detail, and then pop, you're at the top of the list again.
for those facing that problem, don't call ScrollController from InitState. instead, call it inside the build:

ScrollController _scrollController;
  double _offset = 0.0;

  @override
  Widget build(BuildContext context) {

    _scrollController = new ScrollController(initialScrollOffset: _offset);

and then inside your stream/future builder:

return new ListView.builder(
                controller: _scrollController,
                itemCount: docs.length,
                itemExtent: 150.0,
                itemBuilder: (BuildContext context, int index) {

                  if (_scrollController.offset > 100) { // 100 is arbitrary
                    _offset = _scrollController.offset;
                  }
                  return new SomeCard(doc: docs);
                },
              );

Did not help me.
Whenever I do Navigator.push My current screens initState gets called again. which rests my scrolling position in list. When I come back from detail view it again calls initState and everything again. Which takes me back to 0th position in listivew.

I am having listview with multiple child listivew in it who has stream builder which gets the data from frirestore. Please help me with this.

You can preserve scroll position using PageStorageKey property in ListView.

@dblokhin
Copy link

But if a page has many images ,you can see that image blink as if render again. I use image cache
solve this. sorry for my poor english!

@lishuhao did you resolve this problem? Please tell if so.

@TreeLiked
Copy link

I found flutter_gallery has the same problem

I met the same problem, have you solved the problem我也遇到了相同的问题,请问您解决了吗?

@LoverJoker
Copy link

I found flutter_gallery has the same problem

I met the same problem, have you solved the problem我也遇到了相同的问题,请问您解决了吗?

I use static variable to fix the problem. chang index when user switch tab. like this

Global static variable
static int homeTapIndex = 0;

homePage

int _currentIndex = Global.homeTapIndex;

body: IndexedStack(
          index: _currentIndex,
          children: _bodyItem,
        )


onTap: (int index) {
        Global.homeTapIndex = index;
        setState(() {
          _currentIndex = index;
        });
      }

@MaxTenco
Copy link

MaxTenco commented Mar 30, 2020

Hi, I have same problem with a StatefulWidget, the widget rebuilds after Navigator.push()/pop().
Is there a solution?
Thanks

@TreeLiked
Copy link

Hi, I have same problem with a StatefulWidget, the widget rebuilds after Navigator.push()/pop().
Is there a solution?
Thanks

same, but not a solution yet, it almost makes me mad

@LoverJoker
Copy link

Hi, I have same problem with a StatefulWidget, the widget rebuilds after Navigator.push()/pop().
Is there a solution?
Thanks

same, but not a solution yet, it almost makes me mad

maybe you can try it.

home

body: IndexedStack(
          index: _currentIndex,
          children: _bodyItem,
)
 static int homeTapIndex = 0;
 int _currentIndex = homeTapIndex;
final _bodyItem = [
      DeviceList(key: PageStorageKey('deviceList')),
      User(),
    ];

if you change tab

onTap: (int index) {
        PumpGlobal.homeTapIndex = index;
        setState(() {
          _currentIndex = index;
        });
      },

my email address is we@jokerliang.cn that if you dont understand

@lock
Copy link

lock bot commented Apr 19, 2020

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.

@lock lock bot locked and limited conversation to collaborators Apr 19, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
f: routes Navigator, Router, and related APIs. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

No branches or pull requests