Riverpod select() 在重建列表视图子项之前运行
Riverpod select() runs before list view children are rebuilt
我正在使用 Riverpod (package:flutter_riverpod v1.0.3
) 来管理我的 Flutter 应用程序中的状态。我想要一个基于模型中的项目构建的小部件列表。每个列表项 Widget 使用 provider.select
在其索引处选择相应的模型项。请参阅以下示例应用程序:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(const MyApp());
}
final provider = StateNotifierProvider<FruitStateNotifier, List<String>>((ref) {
return FruitStateNotifier(['apricot', 'blueberry', 'cherry']);
});
class FruitStateNotifier extends StateNotifier<List<String>> {
FruitStateNotifier(List<String> fruits) : super(fruits);
void update(List<String> fruits) => state = fruits;
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const ProviderScope(
child: MaterialApp(
home: Scaffold(
body: FruitList(),
),
),
);
}
}
class FruitList extends ConsumerWidget {
const FruitList({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return ListView.builder(
itemCount: ref.watch(provider.select((p) => p.length)),
itemBuilder: (context, index) {
return ListTile(
title: Text('$index) ${ref.watch(provider.select((p) => p[index]))}'),
trailing: IconButton(
onPressed: () {
final fruits = ref.read(provider);
final newFruits = List.of(fruits)..removeAt(index);
ref.read(provider.notifier).update(newFruits);
},
icon: const Icon(Icons.delete),
),
);
},
);
}
}
然而,当删除元素时,select
函数是 运行,但似乎 ListView
的项目计数尚未更新以匹配模型的项目数数。这导致列表中的最后一个 Widget 没有对应的模型项,因此我们得到一个错误:
[ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: An exception was thrown while building StateNotifierProvider<FruitStateNotifier, List<String>>#82be7.
Thrown exception:
RangeError (index): Invalid value: Not in inclusive range 0..1: 2
Stack trace:
#0 List.[] (dart:core-patch/growable_array.dart:281:36)
#1 FruitList.build.<anonymous closure>.<anonymous closure>
#2 _ProviderSelector._select.<anonymous closure>
#3 ResultData.map
#4 _ProviderSelector._select
#5 _ProviderSelector._selectOnChange
#6 _ProviderSelector.listen.<anonymous closure>
#7 _rootRunBinary (dart:async/zone.dart:1450:47)
#8 _CustomZone.runBinary (dart:async/zone.dart:1342:19)
#9 _CustomZone.runBinaryGuarded (dart:async/zone.dart:1252:7)
#10 ProviderElementBase._notifyListeners.<anonymous closure>
#11 ResultData.map
#12 ProviderElementBase._notifyListeners
#13 ProviderElementBase.setState
#14 StateNotifierProvider.create.listener
#15 StateNotifier.state=
#16 FruitStateNotifier.update
#17 FruitList.build.<anonymous closure>.<anonymous closure>
#18 _InkResponseState._handleTap
#19 GestureRecognizer.invokeCallback
#20 TapGestureRecognizer.handleTapUp
#21 BaseTapGestureRecognizer._checkUp
#22 BaseTapGestureRecognizer.handlePrimaryPointer
#23 PrimaryPointerGestureRecognizer.handleEvent
#24 PointerRouter._dispatch
#25 PointerRouter._dispatchEventToRoutes.<anonymous closure>
#26 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:539:8)
#27 PointerRouter._dispatchEventToRoutes
#28 PointerRouter.route
#29 GestureBinding.handleEvent
#30 GestureBinding.dispatchEvent
#31 RendererBinding.dispatchEvent
#32 GestureBinding._handlePointerEventImmediately
#33 GestureBinding.handlePointerEvent
#34 GestureBinding._flushPointerEventQueue
#35 GestureBinding._handlePointerDataPacket
#36 _rootRunUnary (dart:async/zone.dart:1442:13)
#37 _CustomZone.runUnary (dart:async/zone.dart:1335:19)
#38 _CustomZone.runUnaryGuarded (dart:async/zone.dart:1244:7)
#39 _invoke1 (dart:ui/hooks.dart:170:10)
#40 PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:331:7)
#41 _dispatchPointerDataPacket (dart:ui/hooks.dart:94:31)
#0 _fallbackOnErrorForProvider
#1 _ProviderSelector.listen
#2 ProviderContainer.listen
#3 ConsumerStatefulElement.watch.<anonymous closure>
#4 _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:453:23)
#5 ConsumerStatefulElement.watch
#6 FruitList.build.<anonymous closure>
#7 SliverChildBuilderDelegate.build
#8 SliverMultiBoxAdaptorElement._build
#9 SliverMultiBoxAdaptorElement.createChild.<anonymous closure>
#10 BuildOwner.buildScope
#11 SliverMultiBoxAdaptorElement.createChild
#12 RenderSliverMultiBoxAdaptor._createOrObtainChild.<anonymous closure>
#13 RenderObject.invokeLayoutCallback.<anonymous closure>
#14 PipelineOwner._enableMutationsToDirtySubtrees
#15 RenderObject.invokeLayoutCallback
#16 RenderSliverMultiBoxAdaptor._createOrObtainChild
#17 RenderSliverMultiBoxAdaptor.insertAndLayoutChild
#18 RenderSliverList.performLayout.advance
#19 RenderSliverList.performLayout
#20 RenderObject.layout
#21 RenderSliverEdgeInsetsPadding.performLayout
#22 RenderSliverPadding.performLayout
#23 RenderObject.layout
#24 RenderViewportBase.layoutChildSequence
#25 RenderViewport._attemptLayout
#26 RenderViewport.performLayout
#27 RenderObject.layout
#28 RenderProxyBoxMixin.performLayout
#29 RenderObject.layout
#30 RenderProxyBoxMixin.performLayout
#31 RenderObject.layout
#32 RenderProxyBoxMixin.performLayout
#33 RenderObject.layout
#34 RenderProxyBoxMixin.performLayout
#35 RenderObject.layout
#36 RenderProxyBoxMixin.performLayout
#37 RenderObject.layout
#38 RenderProxyBoxMixin.performLayout
#39 RenderObject.layout
#40 RenderProxyBoxMixin.performLayout
#41 RenderObject.layout
#42 RenderProxyBoxMixin.performLayout
#43 RenderCustomPaint.performLayout
#44 RenderObject.layout
#45 RenderProxyBoxMixin.performLayout
#46 RenderObject.layout
#47 RenderProxyBoxMixin.performLayout
#48 RenderObject.layout
#49 RenderProxyBoxMixin.performLayout
#50 RenderObject.layout
#51 RenderProxyBoxMixin.performLayout
#52 RenderObject.layout
#53 MultiChildLayoutDelegate.layoutChild
#54 _ScaffoldLayout.performLayout
#55 MultiChildLayoutDelegate._callPerformLayout
#56 RenderCustomMultiChildLayoutBox.performLayout
#57 RenderObject.layout
#58 RenderProxyBoxMixin.performLayout
#59 RenderObject.layout
#60 RenderProxyBoxMixin.performLayout
#61 _RenderCustomClip.performLayout
#62 RenderObject.layout
#63 RenderProxyBoxMixin.performLayout
#64 RenderObject.layout
#65 RenderProxyBoxMixin.performLayout
#66 RenderObject.layout
#67 RenderProxyBoxMixin.performLayout
#68 RenderObject.layout
#69 RenderProxyBoxMixin.performLayout
#70 RenderObject.layout
#71 RenderProxyBoxMixin.performLayout
#72 RenderObject.layout
#73 RenderProxyBoxMixin.performLayout
#74 RenderObject.layout
#75 RenderProxyBoxMixin.performLayout
#76 RenderObject.layout
#77 RenderProxyBoxMixin.performLayout
#78 RenderObject.layout
#79 RenderProxyBoxMixin.performLayout
#80 RenderOffstage.performLayout
#81 RenderObject.layout
#82 RenderProxyBoxMixin.performLayout
#83 RenderObject.layout
#84 _RenderTheatre.performLayout
#85 RenderObject.layout
#86 RenderProxyBoxMixin.performLayout
#87 RenderObject.layout
#88 RenderProxyBoxMixin.performLayout
#89 RenderObject.layout
#90 RenderProxyBoxMixin.performLayout
#91 RenderObject.layout
#92 RenderProxyBoxMixin.performLayout
#93 RenderCustomPaint.performLayout
#94 RenderObject.layout
#95 RenderProxyBoxMixin.performLayout
#96 RenderObject.layout
#97 RenderProxyBoxMixin.performLayout
#98 RenderObject.layout
#99 RenderProxyBoxMixin.performLayout
#100 RenderObject.layout
#101 RenderProxyBoxMixin.performLayout
#102 RenderObject.layout
#103 RenderView.performLayout
#104 RenderObject._layoutWithoutResize
#105 PipelineOwner.flushLayout
#106 RendererBinding.drawFrame
#107 WidgetsBinding.drawFrame
#108 RendererBinding._handlePersistentFrameCallback
#109 SchedulerBinding._invokeFrameCallback
#110 SchedulerBinding.handleDrawFrame
#111 SchedulerBinding.scheduleWarmUpFrame.<anonymous closure>
#112 _rootRun (dart:async/zone.dart:1418:47)
#113 _CustomZone.run (dart:async/zone.dart:1328:19)
#114 _CustomZone.runGuarded (dart:async/zone.dart:1236:7)
#115 _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1276:23)
#116 _rootRun (dart:async/zone.dart:1426:13)
#117 _CustomZone.run (dart:async/zone.dart:1328:19)
#118 _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:1260:23)
#119 Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
#120 _Timer._runTimers (dart:isolate-patch/timer_impl.dart:395:19)
#121 _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:426:5)
#122 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:192:12)
如何在没有这个问题的情况下构建列表及其子项?
在这里使用 select
没有任何意义,您没有优化重建,因为无论如何列表中的项目数发生变化时都会重建。
你在这里能做的最好的事情就是只观察一次状态并将其分配给一个变量:
final fruitList = ref.watch(provider);
然后只需使用变量获取长度并构建列表项。
因此生成的代码将是:
class FruitList extends ConsumerWidget {
const FruitList({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final fruitList = ref.watch(provider);
return ListView.builder(
itemCount: fruitList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('$index) ${fruitList[index]}'),
trailing: IconButton(
onPressed: () {
final newFruits = List.of(fruitList)..removeAt(index);
ref.read(provider.notifier).update(newFruits);
},
icon: const Icon(Icons.delete),
),
);
},
);
}
}
试试这个:
class FruitList extends ConsumerWidget {
const FruitList({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final list = ref.watch(provider);
return ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('$index) ${list[index]}'),
trailing: IconButton(
onPressed: () {
final fruits = ref.read(provider);
final newFruits = List.of(fruits)..removeAt(index);
ref.read(provider.notifier).update(newFruits);
},
icon: const Icon(Icons.delete),
),
);
},
);
}
}
我正在使用 Riverpod (package:flutter_riverpod v1.0.3
) 来管理我的 Flutter 应用程序中的状态。我想要一个基于模型中的项目构建的小部件列表。每个列表项 Widget 使用 provider.select
在其索引处选择相应的模型项。请参阅以下示例应用程序:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(const MyApp());
}
final provider = StateNotifierProvider<FruitStateNotifier, List<String>>((ref) {
return FruitStateNotifier(['apricot', 'blueberry', 'cherry']);
});
class FruitStateNotifier extends StateNotifier<List<String>> {
FruitStateNotifier(List<String> fruits) : super(fruits);
void update(List<String> fruits) => state = fruits;
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const ProviderScope(
child: MaterialApp(
home: Scaffold(
body: FruitList(),
),
),
);
}
}
class FruitList extends ConsumerWidget {
const FruitList({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return ListView.builder(
itemCount: ref.watch(provider.select((p) => p.length)),
itemBuilder: (context, index) {
return ListTile(
title: Text('$index) ${ref.watch(provider.select((p) => p[index]))}'),
trailing: IconButton(
onPressed: () {
final fruits = ref.read(provider);
final newFruits = List.of(fruits)..removeAt(index);
ref.read(provider.notifier).update(newFruits);
},
icon: const Icon(Icons.delete),
),
);
},
);
}
}
然而,当删除元素时,select
函数是 运行,但似乎 ListView
的项目计数尚未更新以匹配模型的项目数数。这导致列表中的最后一个 Widget 没有对应的模型项,因此我们得到一个错误:
[ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: An exception was thrown while building StateNotifierProvider<FruitStateNotifier, List<String>>#82be7.
Thrown exception:
RangeError (index): Invalid value: Not in inclusive range 0..1: 2
Stack trace:
#0 List.[] (dart:core-patch/growable_array.dart:281:36)
#1 FruitList.build.<anonymous closure>.<anonymous closure>
#2 _ProviderSelector._select.<anonymous closure>
#3 ResultData.map
#4 _ProviderSelector._select
#5 _ProviderSelector._selectOnChange
#6 _ProviderSelector.listen.<anonymous closure>
#7 _rootRunBinary (dart:async/zone.dart:1450:47)
#8 _CustomZone.runBinary (dart:async/zone.dart:1342:19)
#9 _CustomZone.runBinaryGuarded (dart:async/zone.dart:1252:7)
#10 ProviderElementBase._notifyListeners.<anonymous closure>
#11 ResultData.map
#12 ProviderElementBase._notifyListeners
#13 ProviderElementBase.setState
#14 StateNotifierProvider.create.listener
#15 StateNotifier.state=
#16 FruitStateNotifier.update
#17 FruitList.build.<anonymous closure>.<anonymous closure>
#18 _InkResponseState._handleTap
#19 GestureRecognizer.invokeCallback
#20 TapGestureRecognizer.handleTapUp
#21 BaseTapGestureRecognizer._checkUp
#22 BaseTapGestureRecognizer.handlePrimaryPointer
#23 PrimaryPointerGestureRecognizer.handleEvent
#24 PointerRouter._dispatch
#25 PointerRouter._dispatchEventToRoutes.<anonymous closure>
#26 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:539:8)
#27 PointerRouter._dispatchEventToRoutes
#28 PointerRouter.route
#29 GestureBinding.handleEvent
#30 GestureBinding.dispatchEvent
#31 RendererBinding.dispatchEvent
#32 GestureBinding._handlePointerEventImmediately
#33 GestureBinding.handlePointerEvent
#34 GestureBinding._flushPointerEventQueue
#35 GestureBinding._handlePointerDataPacket
#36 _rootRunUnary (dart:async/zone.dart:1442:13)
#37 _CustomZone.runUnary (dart:async/zone.dart:1335:19)
#38 _CustomZone.runUnaryGuarded (dart:async/zone.dart:1244:7)
#39 _invoke1 (dart:ui/hooks.dart:170:10)
#40 PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:331:7)
#41 _dispatchPointerDataPacket (dart:ui/hooks.dart:94:31)
#0 _fallbackOnErrorForProvider
#1 _ProviderSelector.listen
#2 ProviderContainer.listen
#3 ConsumerStatefulElement.watch.<anonymous closure>
#4 _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:453:23)
#5 ConsumerStatefulElement.watch
#6 FruitList.build.<anonymous closure>
#7 SliverChildBuilderDelegate.build
#8 SliverMultiBoxAdaptorElement._build
#9 SliverMultiBoxAdaptorElement.createChild.<anonymous closure>
#10 BuildOwner.buildScope
#11 SliverMultiBoxAdaptorElement.createChild
#12 RenderSliverMultiBoxAdaptor._createOrObtainChild.<anonymous closure>
#13 RenderObject.invokeLayoutCallback.<anonymous closure>
#14 PipelineOwner._enableMutationsToDirtySubtrees
#15 RenderObject.invokeLayoutCallback
#16 RenderSliverMultiBoxAdaptor._createOrObtainChild
#17 RenderSliverMultiBoxAdaptor.insertAndLayoutChild
#18 RenderSliverList.performLayout.advance
#19 RenderSliverList.performLayout
#20 RenderObject.layout
#21 RenderSliverEdgeInsetsPadding.performLayout
#22 RenderSliverPadding.performLayout
#23 RenderObject.layout
#24 RenderViewportBase.layoutChildSequence
#25 RenderViewport._attemptLayout
#26 RenderViewport.performLayout
#27 RenderObject.layout
#28 RenderProxyBoxMixin.performLayout
#29 RenderObject.layout
#30 RenderProxyBoxMixin.performLayout
#31 RenderObject.layout
#32 RenderProxyBoxMixin.performLayout
#33 RenderObject.layout
#34 RenderProxyBoxMixin.performLayout
#35 RenderObject.layout
#36 RenderProxyBoxMixin.performLayout
#37 RenderObject.layout
#38 RenderProxyBoxMixin.performLayout
#39 RenderObject.layout
#40 RenderProxyBoxMixin.performLayout
#41 RenderObject.layout
#42 RenderProxyBoxMixin.performLayout
#43 RenderCustomPaint.performLayout
#44 RenderObject.layout
#45 RenderProxyBoxMixin.performLayout
#46 RenderObject.layout
#47 RenderProxyBoxMixin.performLayout
#48 RenderObject.layout
#49 RenderProxyBoxMixin.performLayout
#50 RenderObject.layout
#51 RenderProxyBoxMixin.performLayout
#52 RenderObject.layout
#53 MultiChildLayoutDelegate.layoutChild
#54 _ScaffoldLayout.performLayout
#55 MultiChildLayoutDelegate._callPerformLayout
#56 RenderCustomMultiChildLayoutBox.performLayout
#57 RenderObject.layout
#58 RenderProxyBoxMixin.performLayout
#59 RenderObject.layout
#60 RenderProxyBoxMixin.performLayout
#61 _RenderCustomClip.performLayout
#62 RenderObject.layout
#63 RenderProxyBoxMixin.performLayout
#64 RenderObject.layout
#65 RenderProxyBoxMixin.performLayout
#66 RenderObject.layout
#67 RenderProxyBoxMixin.performLayout
#68 RenderObject.layout
#69 RenderProxyBoxMixin.performLayout
#70 RenderObject.layout
#71 RenderProxyBoxMixin.performLayout
#72 RenderObject.layout
#73 RenderProxyBoxMixin.performLayout
#74 RenderObject.layout
#75 RenderProxyBoxMixin.performLayout
#76 RenderObject.layout
#77 RenderProxyBoxMixin.performLayout
#78 RenderObject.layout
#79 RenderProxyBoxMixin.performLayout
#80 RenderOffstage.performLayout
#81 RenderObject.layout
#82 RenderProxyBoxMixin.performLayout
#83 RenderObject.layout
#84 _RenderTheatre.performLayout
#85 RenderObject.layout
#86 RenderProxyBoxMixin.performLayout
#87 RenderObject.layout
#88 RenderProxyBoxMixin.performLayout
#89 RenderObject.layout
#90 RenderProxyBoxMixin.performLayout
#91 RenderObject.layout
#92 RenderProxyBoxMixin.performLayout
#93 RenderCustomPaint.performLayout
#94 RenderObject.layout
#95 RenderProxyBoxMixin.performLayout
#96 RenderObject.layout
#97 RenderProxyBoxMixin.performLayout
#98 RenderObject.layout
#99 RenderProxyBoxMixin.performLayout
#100 RenderObject.layout
#101 RenderProxyBoxMixin.performLayout
#102 RenderObject.layout
#103 RenderView.performLayout
#104 RenderObject._layoutWithoutResize
#105 PipelineOwner.flushLayout
#106 RendererBinding.drawFrame
#107 WidgetsBinding.drawFrame
#108 RendererBinding._handlePersistentFrameCallback
#109 SchedulerBinding._invokeFrameCallback
#110 SchedulerBinding.handleDrawFrame
#111 SchedulerBinding.scheduleWarmUpFrame.<anonymous closure>
#112 _rootRun (dart:async/zone.dart:1418:47)
#113 _CustomZone.run (dart:async/zone.dart:1328:19)
#114 _CustomZone.runGuarded (dart:async/zone.dart:1236:7)
#115 _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1276:23)
#116 _rootRun (dart:async/zone.dart:1426:13)
#117 _CustomZone.run (dart:async/zone.dart:1328:19)
#118 _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:1260:23)
#119 Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
#120 _Timer._runTimers (dart:isolate-patch/timer_impl.dart:395:19)
#121 _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:426:5)
#122 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:192:12)
如何在没有这个问题的情况下构建列表及其子项?
在这里使用 select
没有任何意义,您没有优化重建,因为无论如何列表中的项目数发生变化时都会重建。
你在这里能做的最好的事情就是只观察一次状态并将其分配给一个变量:
final fruitList = ref.watch(provider);
然后只需使用变量获取长度并构建列表项。
因此生成的代码将是:
class FruitList extends ConsumerWidget {
const FruitList({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final fruitList = ref.watch(provider);
return ListView.builder(
itemCount: fruitList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('$index) ${fruitList[index]}'),
trailing: IconButton(
onPressed: () {
final newFruits = List.of(fruitList)..removeAt(index);
ref.read(provider.notifier).update(newFruits);
},
icon: const Icon(Icons.delete),
),
);
},
);
}
}
试试这个:
class FruitList extends ConsumerWidget {
const FruitList({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final list = ref.watch(provider);
return ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('$index) ${list[index]}'),
trailing: IconButton(
onPressed: () {
final fruits = ref.read(provider);
final newFruits = List.of(fruits)..removeAt(index);
ref.read(provider.notifier).update(newFruits);
},
icon: const Icon(Icons.delete),
),
);
},
);
}
}