StateProvider
- 데이터를 접근할 수 있도록 해주는 역할
- 일반 provider의 경우 read-only이기 때문에 값 수정 불가능
- 하지만.... 유저 인터렉션으로 인해 값이 변경될 경우가 다수
- 해당 문제점을 해결하기 위해 값을 변경할 수 있도록 하는
StateProvider
사용
StateProvider
에서는 String, bool, integer, enum 등 간단한 값 처리 가능
- 사용자가 데이터 로드 버튼을 클릭했을 시 로딩 화면이 출력된 후 작업이 완료되도록 하고 싶을 경우 아래와 같이 처리하면 됨
final isLoadingProvider = StateProvider<bool>((ref) {
return false;
})
class HomePage extends ConsumerWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final isLoading = ref.watch(isLoadingProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Riverpod'),
),
body: Center(
child: !isLoading
? ElevatedButton(
child: Text(
'Load Data',
style: Theme.of(context)
.textTheme
.bodyText1
?.copyWith(color: Colors.white),
),
onPressed: () {
ref.read(isLoadingProvider.notifier).state = true;
Future.delayed(const Duration(seconds: 3), () {
ref.read(isLoadingProvider.notifier).state = false;
});
},
)
: const CircularProgressIndicator(),
),
);
}
}
- ref.watch라는 키워드를 통해 프로바이더의 상태를 모니터링하는 상태. isLoadingProvider 변수가 변경되는 즉시 위젯은 재빌드
- isLoading이라는 값에 따라 위젯이 조건별로 렌더링
- 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});
@override
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