providers의 빠른 초기화(Eager initialization)
모든 providers는 기본적으로 느리게(Lazy) 초기화됩니다. 즉, provider는 처음 사용될 때만 초기화됩니다. 이는 애플리케이션의 특정 부분에서만 사용되는 provider에 유용합니다.
안타깝게도 Dart의 작동 방식(tree shaking 목적)으로 인해 provider를 이른 초기화(eagerly initialized)해야 하는 것으로 플래그를 지정할 수 있는 방법은 없습니다. 그러나 한 가지 해결책은 애플리케이션의 루트에서 초기화하려는 providers를 강제로 읽는(read) 것입니다.
권장되는 접근 방식은 ProviderScope
바로 아래에 배치된 소비자(Consumer)에서 provider를 단순히 "watch"하는 것입니다:
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return const _EagerInitialization(
// TODO: 여기에서 앱 렌더링
child: MaterialApp(),
);
}
}
class _EagerInitialization extends ConsumerWidget {
const _EagerInitialization({required this.child});
final Widget child;
Widget build(BuildContext context, WidgetRef ref) {
// providers를 감시(watch)하여 providers를 빠른 초기화합니다.
// "watch"를 사용하면 provider가 폐기(disposed)되지 않고 계속 살아(alive) 있습니다.
ref.watch(myProvider);
return child;
}
}
초기화 소비자(initialization consumer)를 "MyApp" 또는 공개 위젯에 넣는 것을 고려해 보세요. 이렇게 하면 메인(main)에서 로직을 제거하여 테스트에서 동일한 동작을 사용할 수 있습니다.
FAQ
provider가 변경되면 전체 애플리케이션이 다시 빌드되지 않나요?
아니요, 그렇지 않습니다.
위의 샘플에서 열심히 초기화를 담당하는 Consumer는 별도의 위젯으로, child
을 반환하는 것 외에는 아무것도 하지 않습니다.
핵심은 MaterialApp
자체를 인스턴스화하는 것이 아니라 child
을 반환한다는 것입니다.
즉, _EagerInitialization
이 리빌드되더라도 child
변수는 변경되지 않습니다.
그리고 위젯이 변경되지 않으면 Flutter는 위젯을 다시 빌드하지 않습니다.
따라서 다른 위젯이 해당 provider를 수신하고 있지 않는 한 _EagerInitialization
만 리빌드됩니다.
이 접근 방식을 사용하면 로딩 및 오류 상태를 어떻게 처리할 수 있나요?
Consumer
에서 일반적으로 처리하는 것처럼 로딩/오류 상태를 처리할 수 있습니다.
_EagerInitialization
은 provider가 "loading" 상태인지 확인하고, 만약 그렇다면 child
대신 CircularProgressIndicator
를 반환할 수 있습니다:
class _EagerInitialization extends ConsumerWidget {
const _EagerInitialization({required this.child});
final Widget child;
Widget build(BuildContext context, WidgetRef ref) {
final result = ref.watch(myProvider);
// 오류 상태 및 로딩 상태 처리
if (result.isLoading) {
return const CircularProgressIndicator();
} else if (result.hasError) {
return const Text('Oopsy!');
}
return child;
}
}
로딩/오류 상태를 처리했지만 다른 Consumer는 여전히 AsyncValue를 받습니다! 모든 위젯에서 로딩/에러 상태를 처리하지 않아도 되는 방법이 있나요?
provider가 AsyncValue
를 노출하지 않도록 하는 대신 위젯이 AsyncValue.requireValue
를 사용하도록 할 수 있습니다.
이렇게 하면 패턴 매칭을 수행하지 않고도 데이터를 읽을 수 있습니다. 그리고 버그가 발생하면 명확한 메시지와 함께 예외를 던집니다.
// 이른 초기화된 provider.
Future<String> example(ExampleRef ref) async => 'Hello world';
class MyConsumer extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final result = ref.watch(exampleProvider);
/// provider가 올바르게 초기화되었다면, "requireValue"로 데이터를 직접 읽을 수 있습니다.
return Text(result.requireValue);
}
}
이러한 경우 로딩/오류 상태를 노출하지 않는 방법(스코핑(scoping)에 의존)이 있지만 일반적으로 그렇게 하지 않는 것이 좋습니다.
두 개의 providers를 만들고 오버라이드를 사용하는 복잡성을 감수할 가치가 없습니다.