DEBUG HELP: Flutter StatefulWidget doesn't maintain state(s) after scrolled out of view
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.
Kafeaulait
Updated on December 04, 2022Comments
-
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 betrue
. The object itself (of class_PlaceWidgetState
), however, is reported to bedefunct, 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'sfavorited
state totrue
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
andExpansionTile
(e.g. this question), but I have no idea how to translate the solutions to those problems to this one.-
Günter Zöchbauer about 6 yearsIt'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 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 about 6 yearsSure,
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 about 6 yearsThanks! I cooked up a dirty solution with a static variable in
MyApp
scope to keep track of the states and check the state in thebuild()
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 about 6 yearsmedium.com/@mehmetf_71205/inheriting-widgets-b7ac56dbbeb1 might be interesting to you. Redux is also often used to maintain state, commonly together with InheritedWidget.
-
-
Kafeaulait about 6 yearsFor 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 anInheritedWidget
. In Jonah's recommendation, we further update_isChecked
by callinginheritedWidget.of(context)
and then access/update the state that_isChecked
should be