Flutter: Riverpod - StateProvider의 종류 및 사용 방법

yeahsilver·2023년 6월 19일
0
post-thumbnail

StateProvider

  • 데이터를 접근할 수 있도록 해주는 역할
  • 일반 provider의 경우 read-only이기 때문에 값 수정 불가능
  • 하지만.... 유저 인터렉션으로 인해 값이 변경될 경우가 다수
  • 해당 문제점을 해결하기 위해 값을 변경할 수 있도록 하는 StateProvider 사용
  • StateProvider에서는 String, bool, integer, enum 등 간단한 값 처리 가능
  • 사용자가 데이터 로드 버튼을 클릭했을 시 로딩 화면이 출력된 후 작업이 완료되도록 하고 싶을 경우 아래와 같이 처리하면 됨
// 초기 값이 false인 provider 생성
final isLoadingProvider = StateProvider<bool>((ref) {
	return false;
})
class HomePage extends ConsumerWidget {
  const HomePage({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    // 1 --------------------------------------
    final isLoading = ref.watch(isLoadingProvider);
    return Scaffold(
      appBar: AppBar(
        title: const Text('Riverpod'),
      ),
      body: Center(
        // 2 --------------------------------------
        child: !isLoading 
            ? ElevatedButton(
                child: Text(
                  'Load Data',
                  style: Theme.of(context)
                      .textTheme
                      .bodyText1
                      ?.copyWith(color: Colors.white),
                ),
                onPressed: () {
                  // 3 --------------------------------------
                  ref.read(isLoadingProvider.notifier).state = true;
                  Future.delayed(const Duration(seconds: 3), () {
                    ref.read(isLoadingProvider.notifier).state = false;
                  });
                },
              )
            : const CircularProgressIndicator(),
      ),
    );
  }
}

  1. ref.watch라는 키워드를 통해 프로바이더의 상태를 모니터링하는 상태. isLoadingProvider 변수가 변경되는 즉시 위젯은 재빌드
  2. isLoading이라는 값에 따라 위젯이 조건별로 렌더링
  3. onPressed 버튼 클릭 시 isLoadingProvider 값 변경
    • 여기서 .notifier라는 키워드를 사용하는데, 해당 키워드는 프로바이더의 현재 스냅샷을 제공
    • 해당 코드의 경우 provider의 상태에 접근하여 값을 변경

StateNotifier & StateNotifierProvider

StateNotifier

  • 하나의 상태만 저장하는 프로바이더
  • 클래스 형태로 사용 가능
    class HomeNotifier extends StaetNotifier<List<String>>{}
  • 상태값이 변경된다면, 관찰되고있는 위젯이 재빌드
  • StateNotifier를 클래스로 사용하고 싶다면, 무조건 초기값 세팅을 진행해야함
    class HomeNotifier extends StateNotifier<List<String>>{
        HomeNotifier(): super(["Hello", "World"]);
    }
  • 초기 값을 부여했으면, 해당 클래스를 원래 클래스를 사용하는 것과 동일하게 처리 가능
  • StateNotifier의 경우 클래스 내부에 있는 상태를 접근할 수 있기에, 초기값을 바로 불러올 수 있고, 새로운 값도 넣을 수 있다.

StateNotifierProvider

  • StateNotifier가 정의되어있지 않은 클래스에서 state를 가지고 오고 싶을 경우 사용
  • StateNotifier를 수신하고 listen하고 있는 프로바이더
final homeProvider = StaetNotifierProvider<HomeNotifier>, List<String>>((ref) {
	return HomeNotifier();
})
  • StateNotifierProvider를 사용할 때에는 두가지를 꼭 정의해야한다
    - notifier (HomeNotifier)
    • state의 값 타입 (List<String>)

FutureProvider

  • 비동기 코드를 처리할 때 사용
  • API를 통해 데이터를 가지고 올 때 주로 FutureProvider를 사용
final userProvider = FutureProvider.autoDispose<List<User>>((ref) async {
  final dioClient = ref.read(dioClientProvider);
  final res = await dioClient.get('https://jsonplaceholder.typicode.com/users');
  return (res.data as List).map((e) => User.fromJson(e)).toList();
});
  • API에서 user 리스트를 얻은 후에, 값을 반환
class HomePage extends ConsumerWidget {
  const HomePage({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final user = ref.watch(userProvider);
    return Scaffold(
      appBar: AppBar(
        title: const Text('Riverpod'),
      ),
      body: Center(
        child: user.when(
          data: (data) {
            return Column(
              children: data.map((e) => Text(e.name ?? '')).toList(),
            );
          },
          error: (error, stackTrace) {
            return Text(error.toString());
          },
          loading: () => const CircularProgressIndicator(),
        ),
      ),
    );
  }
}
  • 여기서 프로바이더의 state를 listen하고 있는 프로바이더를 생성한 뒤에 .watch() 메소드를 사용하면 서버에서 데이터를 받아오는 것을 체크하여 값을 출력
  • AsyncValue를 사용하는 이유: 비동기 데이터를 안전하게 처리하기 위해서
  • AsyncValue를 사용하게 된다면, 비동기 작업에서 필수적으로 필요한 로딩 / 오류 상태를 잊지 않고 처리할 수 있음

StreamProvider

  • 실시간 API의 값을 stream으로 받아오는 경우에 해당 값을 watch하는 프로바이더
final authStateChangeProvider = StreamProvider.autoDispose<User?>((ref) {
	final firebaseAuth = ref.watch(irebaseAuthProvider);
    return firebaseAuth.authStateChanges();
})
  • 위젯 내부에서 사용 시 아래와 같이 사용
Widget build(BuildContext context, WidgetRef ref) {
  final authStateAsync = ref.watch(authStateChangesProvider);
  return authStateAsync.when(
    data: (user) => user != null ? HomePage() : SignInPage(),
    loading: () => const CircularProgressIndicator(),
    error: (err, stack) => Text('Error: $err'),
  );
}

Reference

profile
Hello yeahsilver's world!

0개의 댓글