这是我最近一直在努力思考的话题。
我认为Remi 的回答缺少的是将Future 数据转换为可操作数据的能力。
当您使用 FutureProvider 接收 Future 数据并使用 when 方法或使用 FutureBuilder 小部件实现 ui 时,它们都会在接收到远程数据时触发重建,因此如果您尝试将值分配给您的 StateProvider 它将在另一个将抛出的重建期间触发重建。
我目前有 2 个解决方法,当我获得更多相关信息时,我将更新我的答案。
对于这个例子,我们将有一个未来的提供者会等待然后返回一个假数据:
final _futureCounterProv = FutureProvider(
(ref) async {
Future.delayed(
Duration(seconds: 3),
);
return Random().nextInt(100);
},
);
Future.microtask 使您能够在当前重建结束后运行操作。
您必须确保您的 StateProvider 依赖项位于 Future.microtask 调用下方的 Consumer 中,或者 Future.microtask 将在每次状态更新时被调用,这将不断重置 StateProvider 的值到未来价值
// this provider will provide the current value of the counter
final _counterProv = StateProvider((ref) => 0);
class Body extends ConsumerWidget {
const Body({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return ref.watch(_futureCounterProv).when(
loading: () {
return const Center(
child: CircularProgressIndicator(),
);
},
error: (error, stackTrace) {
return Text(error.toString());
},
data: (data) {
Future.microtask(
() {
// Assigning the future value to the `StateProvider`
return ref.read(_counterProv.notifier).state = data;
},
);
return Consumer(
builder: (context, ref, _) {
final count = ref.watch(_counterProv);
return Column(
children: [
IconButton(
onPressed: () {
ref
.read(_counterProv.notifier)
.update((value) => value + 1);
},
icon: const Icon(Icons.add),
),
Text(
count.toString(),
),
],
);
},
);
},
);
}
}
StateProvider 有 2 个选项来更新它的值:value setter 和 update 方法,它们都会触发重建。在此解决方法中,我们希望实施不触发重建的状态更新。一种方法是使用 ChangeNotifierProvider 而不是 StateProvider。通过使用ChangeNotifierProvider,我们可以控制我们自己的更新操作,并在需要时调用notifyListeners(这将触发重建)。
您必须确保您的 ChangeNotifierProvider 依赖项位于 updateNoNotify 调用下方的 Consumer 中,否则 ChangeNotifierProvider 将继续重置为未来的值。此外,您还必须确保所有使用此 ChangeNotifierProvider 的小部件都在 updateNoNotify 下方的小部件树中,否则它们将不会被重建,因为我们没有触发重建
// the new `_counterProv`
final _counterProv = ChangeNotifierProvider(
(ref) => _CounterNotifier(),
);
class _CounterNotifier extends ChangeNotifier {
int _value = 0;
int get value => _value;
void update(int Function(int value) update) {
_value = update(_value);
// trigger a rebuild
notifyListeners();
}
void updateNoNotify(int Function(int value) update) {
_value = update(_value);
}
}
// the ui
class Body extends ConsumerWidget {
const Body({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return ref.watch(_futureCounterProv).when(
loading: () {
return const Center(
child: CircularProgressIndicator(),
);
},
error: (error, stackTrace) {
return Text(error.toString());
},
data: (data) {
// calling `updateNoNotify` which does not trigger
// trigger rebuild as it does not call `notifyListeners`
ref.read(_counterProv.notifier).updateNoNotify(
(e) => data,
);
return Consumer(
builder: (context, ref, _) {
final count = ref.watch(_counterProv).value;
return Column(
children: [
IconButton(
onPressed: () {
ref.read(_counterProv.notifier).update(
(value) => value + 1,
);
},
icon: const Icon(Icons.add),
),
Text(
count.toString(),
),
],
);
},
);
},
);
}
}
这些不是safest 解决方法,但它们是workarounds,一旦找到安全的方法,我将更新此答案。