Riverpod을 사용하다보면 다양한 이유로 에러를 마주칠 수 있고, 대부분의 에러 원인은 Riverpod 공식문서를 보면 확인할 수 있다.
내가 Riverpod을 사용하면서 겪었던 에러 상황들과 공식문서를 보면서 디버깅 해나간 것들을 정리한다.
Riverpod Do/Dont 문서를 보게되면 initState와 같이 Provider 외부 context에 의해 값이 초기화되서는 안된다고 나와있다.
Providers should initialize themselves.
They should not be initialized by an external element such as a widget. Failing to do so could cause possible race conditions and unexpected behaviors.
https://riverpod.dev/docs/essentials/do_dont
class WidgetState extends State<MyWidget> {
@override
void initState() {
super.initState();
// Bad: the provider should initialize itself
ref.read(provider).init();
}
}
실제로 비슷한 상황을 만들어 코드를 돌려보면 life-cycle 에러가 발생한다.
(useEffect를 제외하고 그냥 build안에 메소드를 넣어놔도 동일하다)
이때 WidgetBinding.instance.addPostFrameCallback(); 함수 안에 state 값 변경 이벤트를 넣어 buil가 완료된 후 state 변경을 처리하여 에러를 피할 수 있다.
class LoginScreen extends BaseScreen {
static const String route = 'login';
const LoginScreen({super.key});
@override
Widget buildScreen(BuildContext context, WidgetRef ref) {
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(countProvider.notifier).increment();
});
});
return Container();
}
}
하지만 이 또한 에러를 피하는 방법일뿐이라고 여겨지며, Riverpod 제작자 Remi는 onPressed와 같은 메소드 안에 미리 값을 초기화한 후 route를 이동시키는 것을 권장하고 있다.
There is no "one-size fits all" solution to this problem.
If your initialization logic depends on factors external to the provider, often the correct place to put such logic is in the onPressed method of a button triggering navigation:
ElevatedButton(
onPressed: () {
ref.read(provider).init();
Navigator.of(context).push(...);
},
child: Text('Navigate'),
)