DEBUG HELP: Flutter StatefulWidget doesn't maintain state(s) after scrolled out of view

1,713

So, architecture discussions aside, the cause of your problem is in how you are constructing the State objects. By passing the data through the constructor you are ensuring that every time your widgets are rebuilt, their state is reset.

 class PlaceWidget extends StatefulWidget {
  @override
  _PlaceWidgetState createState() {
    return new _PlaceWidgetState(place, false); // woops, always unchecked
  }

  final Place place;
  PlaceWidget(this.place, {Key key}) : super(key: key);
}

class _PlaceWidgetState extends State<PlaceWidget> { ... }

Instead of doing this, use the widget getter inside of your state widget to get access to the place member. Also make sure you only initialize your checked state inside of the State widget - or get it from some external source here.

class _PlaceWidgetState extends State<PlaceWidget> {
  bool _isChecked = false; // only store state in State

  Widget build() {
   final Place place = widget.place;
   // build your stuff.
  }
}

Now the reason your widget keeps getting rebuilt is that in a ListView, offscreen widgets may be destroyed - it is designed for very large lists. To prevent this, you can also use the KeepAlive widget.

Share:
1,713
Kafeaulait
Author by

Kafeaulait

Updated on December 04, 2022

Comments

  • Kafeaulait
    Kafeaulait over 1 year

    I'm playing around with a simple app to learn Flutter. This is the structure of the UI:

    app -- MaterialApp -- HomeScreen (stateful) |- ListView -- PlaceWidget (stateful) |- ListTile

    The PlaceWidget object basically builds and returns a ListTile; its only additional duty is to keep track of the favorited state and builds the UI of the ListTile accordingly.

    The source code is here, including two files:

    • main.dart for the entire app, and
    • places.dart for the http request

    This is how the app behaves: https://gfycat.com/FineBelovedLeafhopper


    On the surface, it looks as though the states of the objects are lost when scrolled out of view, but a bit of debug logging tells me otherwise.

    Suppose I favorite Oto Sushi then scroll it off-screen but keep a pointer to the state object, the object's favorited state will still be true. The object itself (of class _PlaceWidgetState), however, is reported to be defunct, not mounted.
    I can no longer interact with that object. If I tap Oto Sushi one more time, it'll create a new state object and set that object's favorited state to true just like before.

    As long as I do not scroll Oto Sushi off-screen, I can unfavorite it and things behave normally.

    I managed to find similar problems with TextInputField and ExpansionTile (e.g. this question), but I have no idea how to translate the solutions to those problems to this one.

    • Günter Zöchbauer
      Günter Zöchbauer about 6 years
      It's not rendered when it's out of view, therefore doesn't exist and has no state to maintain. You need to maintain the state yourself.
    • Kafeaulait
      Kafeaulait about 6 years
      @GünterZöchbauer can you clarify a bit more on how I should achieve that? When it scrolls back into view, does its build() function get called again? I realize that I can simply keep a variable in a higher scope to keep track of the states, I'm just wondering if there are "good code" standard solutions.
    • Günter Zöchbauer
      Günter Zöchbauer about 6 years
      Sure, build will be called again. build will usually also be called while it's visible. I haven't investigated this in a while, but if it loses state, initState should also be called when it is rendered again when it becomes visible.
    • Kafeaulait
      Kafeaulait about 6 years
      Thanks! I cooked up a dirty solution with a static variable in MyApp scope to keep track of the states and check the state in the build() method; it's working correctly now. If you have advice on how to do this cleanly, I'd greatly appreciate it.
    • Günter Zöchbauer
      Günter Zöchbauer about 6 years
      medium.com/@mehmetf_71205/inheriting-widgets-b7ac56dbbeb1 might be interesting to you. Redux is also often used to maintain state, commonly together with InheritedWidget.
  • Kafeaulait
    Kafeaulait about 6 years
    For future reference, in case someone as dumb as I am tumbles across this problem, the full solution includes: - Keeping track of the state locally inside _PlaceWidgetState, and retrieving the state of each widget by - pulling in the context from an InheritedWidget. In Jonah's recommendation, we further update _isChecked by calling inheritedWidget.of(context) and then access/update the state that _isChecked should be