'StatefulWidgets in ReorderableListView don't keep their state when reordering the list
Here I have a trimmed down page which creates a ReorderableListView
, which has its body set to two RecipeStepWidget
s with UniqueKey
s set (I've also tried this with ValueKey
and ObjectKey
)
import 'package:flutter/material.dart';
import 'consts.dart';
import 'recipeStep.dart';
import 'recipeStepWidget.dart';
class NewRecipePage extends StatefulWidget {
@override
_NewRecipePageState createState() => _NewRecipePageState();
}
class _NewRecipePageState extends State<NewRecipePage> {
final TextEditingController _nameController = TextEditingController();
@override
Widget build(BuildContext context) {
List<RecipeStep> recipeSteps = [];
List<RecipeStepWidget> stepWidgets = [
RecipeStepWidget(key: UniqueKey()),
RecipeStepWidget(key: UniqueKey())
];
void _onReorder(int oldIndex, int newIndex) {
setState(
() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final RecipeStepWidget item = stepWidgets.removeAt(oldIndex);
stepWidgets.insert(newIndex, item);
},
);
}
return Scaffold(
appBar: AppBar(title: Text("New Recipe")),
body: Column(
children: <Widget>[
Expanded(
child: ReorderableListView(
header: Text("Steps"),
onReorder: _onReorder,
children: stepWidgets,
),
),
],
),
);
}
}
The RecipeStepWidget
class is (ignoring includes):
class RecipeStepWidget extends StatefulWidget {
RecipeStepWidget({@required Key key}) : super(key: key);
_RecipeStepWidgetState createState() => _RecipeStepWidgetState();
}
class _RecipeStepWidgetState extends State<RecipeStepWidget> {
TextEditingController _controller = TextEditingController();
TextEditingController _durationLowController = TextEditingController();
TextEditingController _durationHighController = TextEditingController();
bool concurrent = false;
RecipeStep toRecipeStep() {
return RecipeStep(
text: _controller.text,
);
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
controller: _controller,
decoration: InputDecoration(hintText: "Step text"),
),
Row(
children: <Widget>[
Text("Duration min: "),
Container(
width: 40.0,
//height: 100.0,
child: TextField(
controller: _durationLowController,
keyboardType: TextInputType.number,
decoration: InputDecoration(hintText: "0"),
onChanged: (String val) {
if (_durationHighController.text.isEmpty ||
int.parse(val) >
int.parse(_durationHighController.text)) {
_durationHighController.text = val;
}
},
),
),
Text(" max: "),
Container(
width: 40.0,
//height: 100.0,
child: TextField(
controller: _durationHighController,
keyboardType: TextInputType.number,
decoration: InputDecoration(hintText: "0"),
),
),
],
),
Row(
children: <Widget>[
Text("Start concurrently with previous step"),
Checkbox(
value: concurrent,
onChanged: (bool val) => {
setState(() {
concurrent = val;
})
}),
],
),
],
);
}
}
When I edit the textfields or checkboxes in the RecipeStateWidgets
and then reorder them within the list by clicking+dragging them, the widgets get reset to their default state.
Does anyone have any ideas why this is happening? I thought that all I had to do in order to get the ReorderableListView
to work as intended was to set a key on each of its children. Is that not correct?
Thanks!
Solution 1:[1]
I think you can use AutomaticKeepAliveClientMixin like so:
class _RecipeStepWidgetState extends State<RecipeStepWidget> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
Solution 2:[2]
Giving the list item a ValueKey
seems to fix the problem for me. Hopefully helps in your situation as well.
e.g.
List<YourModel> _items = [];
Widget _itemsListWidget() {
return ReorderableListView(
onReorder: (oldIndex, newIndex) {
//
},
children: [
for (int index = 0; index < _items.length; index += 1)
Text(
_items[index],
key: ValueKey(_items[index].id), // <--- This is what you need to add
),
],
);
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | InsolentWorm |
Solution 2 | Chris |