英文:
Flutter: How to prevent entire list rebuilding when provider changes
问题
我有一个使用简单Provider的ListView.builder,每个项目都需要异步加载数据,因此我正在使用FutureBuilder。
当我向列表添加一个项目时,整个列表都会重新构建。这是有道理的,因为它包装在Consumer中,但不幸的是,这意味着现有项目需要重新运行Future,尽管这些项目实际上没有改变。
有没有办法只重新构建需要构建的ListView项目(即新项目)?
英文:
I have a ListView.builder using a Consumer of a simple Provider.
Each of the items needs to load something asynchronously so I'm using FutureBuilder.
When I add an item to the list, the entire list is rebuilt. This makes sense because it's wrapped in the Consumer, but unfortunately it means the existing items need to re-run the future despite those items not actually changing.
class ListWithButton extends StatelessWidget {
const ListWithButton({super.key});
@override
Widget build(BuildContext context) {
return Consumer<MyProvider>(
builder: (_, provider, __) => Column(
children: [
ElevatedButton(
onPressed: () {
provider.addItem();
},
child: const Text("Add Item"),
),
Expanded(
child: ListView.builder(
itemCount: provider.items.length,
itemBuilder: (_, index) => FutureBuilder<Color>(
future: provider.loadSomething(index),
builder: (_, snapshot) => ListTile(
leading: Container(
width: 30,
height: 30,
color: snapshot.connectionState == ConnectionState.done
? snapshot.data!
: Colors.red,
),
title: Text(index.toString()),
),
),
),
),
],
),
);
}
}
class MyProvider extends ChangeNotifier {
final List<int> _items = [];
List<int> get items => _items;
void addItem() {
_items.add(_items.length);
notifyListeners();
}
Future<Color> loadSomething(int i) async {
await Future.delayed(Duration(seconds: 1));
return Colors.green;
}
}
Is there a way I can only rebuild the items in the ListView which need building (i.e. the new items)?
答案1
得分: 1
无法完全阻止列表在添加新子项时重新构建其子部件,但可以通过在构建方法之外初始化并存储Futures来阻止Futures被重新初始化,这在FutureBuilder的文档中是推荐的:
必须在之前获取未来,例如在State.initState、State.didUpdateWidget或State.didChangeDependencies期间。在构造FutureBuilder时不能在State.build或StatelessWidget.build方法调用中创建它。如果将未来与FutureBuilder同时创建,那么每当FutureBuilder的父级重新构建时,异步任务都会重新启动。
为此,您只需像这样更改MyProvider
:
class MyProvider extends ChangeNotifier {
final List<int> _items = [];
final Map<int, Future<Color>> _futureColors = {};
List<int> get items => _items;
void addItem() {
_items.add(_items.length);
notifyListeners();
}
Future<Color>? loadSomething(int i) {
if (!_futureColors.containsKey(i)) {
_futureColors[i] = Future.delayed(
const Duration(seconds: 1),
() => Colors.green,
);
}
return _futureColors[i];
}
}
在这里,我们只在第一次使用给定索引调用loadSomething
时创建新的未来 - 我们将该未来存储在Map中,以后调用loadSomething
时,我们返回已为该索引初始化的未来。 (我之所以使用Map
而不是List
,是因为无法保证该函数将接收有效的List索引)。
还要注意loadSomething
不再是异步函数 - 这很重要,因为我们希望未来同步返回给FutureBuilder
。如果我们异步返回未来,您可能会注意到在添加新元素时,列表的元素有时仍会闪烁为红色。
英文:
You cannot necessarily prevent the list from rebuilding its child widgets when a new child is added, but you can prevent the Futures from being re-initialized by initializing and storing them outside of the build method, which is recommended in the documentation for FutureBuilder:
> The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.
To do this, you need only change MyProvider, like so:
class MyProvider extends ChangeNotifier {
final List<int> _items = [];
final Map<int, Future<Color>> _futureColors = {};
List<int> get items => _items;
void addItem() {
_items.add(_items.length);
notifyListeners();
}
Future<Color>? loadSomething(int i) {
if (!_futureColors.containsKey(i)) {
_futureColors[i] = Future.delayed(
const Duration(seconds: 1),
() => Colors.green,
);
}
return _futureColors[i];
}
}
Here we only create a new future the first time loadSomething
is called with a given index — We store that future in a Map, and on later calls to loadSomething
we return the Future that has already been initialized for that index. (The reason I used a Map
instead of a List
is that there have no guarantees that the function will receive valid List indices).
Note also that loadSomething
is no longer an async
function — This is important, as we want the future to be returned to the FutureBuilder
synchronously. If we return the future asynchronously, you may notice that the elements of the List still flicker red sometimes when you add new elements.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论