참고: riverpod.dev/
Provider, but different | Riverpod
A boilerplate-free and safe way to share state
riverpod.dev
정의
어디서든(widget 등) 변경을 감지(listen)할 수 있는 상태 관리 객체
왜 사용하는가?
- 어디서든 상태값에 접근 가능
- 다른 상태값과 결합하여 사용 용이
- 상태 변화에 영향을 받는 부분에 대해서만 부분 렌더링 (성능 최적화)
- 로깅 등 다른 feature와 결합하여 사용 가능
종류
1. Provider
가장 기본적인 provider 형태
상태값 자체를 리턴한다.
pub.dev/documentation/riverpod/latest/all/Provider-class.html
Provider class - all library - Dart API
Provider class A provider that exposes a read-only value. What is a provider Providers are the most important components of Riverpod. In short, you can think of providers as an access point to a shared state. Providers solve the following problems: Provide
pub.dev
사용 케이스
- 단순히 특정 값을 읽기만 하고 싶은 경우.
- 비동기 처리가 필요 없는 경우.
예제
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// provider 객체 생성.
// 상태값을 리턴하는 콜백함수를 인자로 넣어준다.
final helloWorldProvider = Provider((_) => 'Hello world');
void main() {
runApp(
// riverpod provider를 사용하기 위해서는 전체 위젯을 ProviderScope 위젯으로 감싸야 함.
ProviderScope(
child: MyApp(),
),
);
}
// Riverpod에서 관리하는 Consumerwidger을 상속.
class MyApp extends ConsumerWidget {
@override
Widget build(BuildContext context, ScopedReader watch) {
final String value = watch(helloWorldProvider);
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Example')),
body: Center(
child: Text(value),
),
),
);
}
}
다른 provider와의 결합
final weatherProvider = FutureProvider((ref) async {
// We use `ref.watch` to watch another provider, and we pass it the provider
// that we want to consume. Here: cityProvider
final city = ref.watch(cityProvider);
// We can then use the result to do something based on the value of `cityProvider`.
return fetchWeather(city: city);
});
2. StateProvider
Provider 스펙 + state 프로퍼티에 직접 직접 값 변경이 가능함
사용 케이스
- 상태값 변경이 로직이 단순한 경우
예제
final selectedProductIdProvider = StateProvider<String>((ref) => null);
final productsProvider = StateNotifierProvider<ProductsNotifier>((ref) => ProductsNotifier());
Widget build(BuildContext context, ScopedReader watch) {
final List<Product> products = watch(productsProvider.state);
final selectedProductId = watch(selectedProductIdProvider);
return ListView(
children: [
for (final product in products)
GestureDetector(
onTap: () => selectedProductId.state = product.id,
child: ProductItem(
product: product,
isSelected: selectedProductId.state == product.id,
),
),
],
);
}
3. StateNotifierProvider
StateNotifier를 리턴하는 provider
사용 케이스
- 좀더 복잡한 상태값 변경 로직을 작성하고 싶은 경우
예제
3.1. StateNotifier 정의
기본적으로 상속해서 정의하도록 디자인되어 있음.
StateNotifierBuilder & StateNotifierProvider에서 listen 가능.
class TodosNotifier extends StateNotifier<List<Todo>> {
// 생성자의 인자로 초기 상태값을 입력.
TodosNotifier(): super([]);
void add(Todo todo) {
state = [...state, todo];
}
void remove(String todoId) {
state = [
for (final todo in state)
if (todo.id != todoId) todo,
];
}
void toggle(String todoId) {
state = [
for (final todo in state)
if (todo.id == todoId) todo.copyWith(completed: !todo.completed),
];
}
}
3.2. StateNotifierProvider 정의
final todosProvider = StateNotifierProvider((ref) => TodosNotifier());
3.3. 결합
Widget build(BuildContext context, ScopedReader watch) {
// 상태값 구독.
List<Todo> todos = watch(todosProvider.state);
return ListView(
children: [
for (final todo in todos)
CheckboxListTile(
value: todo.completed,
// StateNotifier의 메서드를 사용해서 상태값 변경.
onChanged: (value) => todosProvider.read(context).toggle(todo.id),
title: Text(todo.description),
),
],
);
}
4. FutureProvider
pub.dev/documentation/riverpod/latest/all/FutureProvider-class.html
FutureProvider class - all library - Dart API
FutureProvider class A provider that asynchronously creates a single value. FutureProvider can be considered as a combination of Provider and FutureBuilder. By using FutureProvider, the UI will be able to read the state of the future synchronously, handle
pub.dev
사용 케이스
- HTTP 통신처럼 비동기 처리가 필요한 경우
예제
4.1. FutureProvider 생성
final configProvider = FutureProvider<Configuration>((ref) async {
final content = json.decode(
await rootBundle.loadString('assets/configurations.json'),
) as Map<String, dynamic>;
return Configuration.fromJson(content);
});
4.2. 위젯에서의 사용
Widget build(BuildContext, ScopedReader watch) {
AsyncValue<Configuration> config = watch(configProvider);
return config.when(
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
data: (config) {
return Text(config.host);
},
);
}
5. StreamProvider
리턴값이 Stream인 것을 제외하고는 FutureProvider과 동일
pub.dev/documentation/riverpod/latest/all/StreamProvider-class.html
StreamProvider class - all library - Dart API
StreamProvider class Creates a stream and expose its latest event. StreamProvider is identical in behavior/usage to FutureProvider, modulo the fact that the value created is a Stream instead of a Future. It can be used to express a value asynchronously loa
pub.dev
사용 케이스
- 웹 소켓 처럼 실시간 양방향 통신이 필요한 경우
- 비동기 처리가 필요한 경우
예제
5.1 StreamProvider 생성
final messageProvider = StreamProvider.autoDispose<String>((ref) async* {
// Open the connection
final channel = IOWebSocketChannel.connect('ws://echo.websocket.org');
// Close the connection when the stream is destroyed
ref.onDispose(() => channel.sink.close());
// Parse the value received and emit a Message instance
await for (final value in channel.stream) {
yield value.toString();
}
});
5.2. 위젯에서의 사용
Widget build(BuildContext, ScopedReader watch) {
AsyncValue<String> message = watch(messageProvider);
return message.when(
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
data: (message) {
return Text(message);
},
);
}
정리
기능 | Provider | StateProvider | StateNotifierProvider | FutureProvider | StreamProvider |
상태값 읽기 | O | O | O | O | O |
상태값 쓰기 | X | O | O | O | O |
자체 메서드 | X | X | O | X | X |
비동기 처리(단방향) | X | X | X | O | O |
비동기 처리(양방향) | X | X | X | X | O |