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, andplaces.dart
for the http requestThis 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.
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.