An exception was thrown while building
エラーが発生するパターンは複数あると思われますが、私が遭遇したパターンの原因と修正方法を記載します。
以下コードで実行エラーが発生しました。
final speedProvider = StateProvider<int>((ref) => 5);
final stepProvider = StateNotifierProvider<StepViewModel, double>(
(ref) => StepViewModel(0, ref));
class StepViewModel extends StateNotifier<double> {
final Ref _ref;
StepViewModel(double state, this._ref) : super(state) {
_ref.read(speedProvider.notifier).addListener((_) {
_startTimer();
});
}
void _startTimer() {
final speed = _ref.read(speedProvider);
....
}
}
======== Exception caught by widgets library =======================================================
The following ProviderException was thrown building Consumer(dirty, dependencies: [UncontrolledProviderScope], state: _ConsumerState#1fc32):
An exception was thrown while building StateProvider<int>#bd119.
Thrown exception:
Concurrent modification during iteration.
Stack trace:
#0 StateNotifier.addListener.<anonymous closure> (package:state_notifier/state_notifier.dart:276:9)
#1 StateNotifier.addListener (package:state_notifier/state_notifier.dart:279:6)
#2 StateProvider.create (package:riverpod/src/state_provider/base.dart:88:37)
#3 ProviderElementBase._buildState (package:riverpod/src/framework/provider_base.dart:481:26)
#4 ProviderElementBase.mount (package:riverpod/src/framework/provider_base.dart:382:5)
#5 _StateReader._create (package:riverpod/src/framework/container.dart:104:11)
#6 _StateReader.getElement (package:riverpod/src/framework/container.dart:92:52)
#7 ProviderContainer.readProviderElement (package:riverpod/src/framework/container.dart:510:19)
#8 ProviderContainer.read (package:riverpod/src/framework/container.dart:298:21)
#9 ProviderElementBase.read (package:riverpod/src/framework/provider_base.dart:661:23)
#10 StepViewModel._startTimer (package:my_app/view_model/step_view_model.dart:44:24)
#11 new StepViewModel.<anonymous closure> (package:my_app/view_model/step_view_model.dart:27:7)
#12 StateNotifier.addListener (package:state_notifier/state_notifier.dart:286:17)
#13 new StepViewModel (package:my_app/view_model/step_view_model.dart:24:39)
#14 stepProvider.<anonymous closure> (package:my_app/view_model/step_view_model.dart:8:14)
#15 _NotifierProvider.create (package:riverpod/src/state_notifier_provider/base.dart:98:29)
#16 ProviderElementBase._buildState (package:riverpod/src/framework/provider_base.dart:481:26)
#17 ProviderElementBase.mount (package:riverpod/src/framework/provider_base.dart:382:5)
#18 _StateReader._create (package:riverpod/src/framework/container.dart:104:11)
#19 _StateReader.getElement (package:riverpod/src/framework/container.dart:92:52)
#20 ProviderContainer.readProviderElement (package:riverpod/src/framework/container.dart:510:19)
#21 ProviderElementBase.watch (package:riverpod/src/framework/provider_base.dart:732:32)
#22 StateNotifierProvider.create (package:riverpod/src/state_notifier_provider/base.dart:54:26)
#23 ProviderElementBase._buildState (package:riverpod/src/framework/provider_base.dart:481:26)
#24 ProviderElementBase.mount (package:riverpod/src/framework/provider_base.dart:382:5)
原因
addListener
メソッドのドキュメントに
The [listener] callback will be called immediately on addition
と書いてある通り、メソッドを読んだ時にコールバックが呼ばれます。
コールバックの中で同じProviderを参照したためエラーが起きました。
修正方法1
コールバック内でProviderを使わないようにすればOKです。
StepViewModel(double state, this._ref) : super(state) {
_ref.read(speedProvider.notifier).addListener((state) {
_startTimer(state);
});
}
void _startTimer(int state) {
final speed = state;
...
}
もしコールバック内でProviderを使いざるを得ない場合は、修正方法2が使えます。
修正方法2
synchronously whenever [state] changes.
Set [fireImmediately] to false if you want to skip the first,
immediate execution of the [listener].
ドキュメントにある通り、 fireImmediately: false
を追加すれば addListener
実行時にコールバックは呼ばれなくなります。
これならコールバック内でProviderを使ってもエラーが起きません。
_ref.read(speedProvider.notifier).addListener((_) {
_startTimer();
}, fireImmediately: false);