flutter pub add flutter_riverpod
flutter pub add riverpod_annotation
flutter pub add dev:riverpod_generator
flutter pub add dev:build_runner
flutter pub add dev:custom_lint
flutter pub add dev:riverpod_lint
pubspec.yaml
name: my_app_name
environment:
sdk: ">=3.0.0 <4.0.0"
flutter: ">=3.0.0"
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^2.5.1
riverpod_annotation: ^2.3.5
dev_dependencies:
build_runner:
custom_lint:
riverpod_generator: ^2.4.0
riverpod_lint: ^2.3.10
작성한 Riverpod을 Generation 해준다. provider.g.dart 파일이 생긴다.
dart run build_runner watch
main.dart
void main() {
runApp(
// Riverpod을 설치하려면 다른 모든 위젯 위에 이 위젯을 추가해야 합니다.
// 이 위젯은 "MyApp" 내부가 아니라 "runApp"에 직접 파라미터로 추가해야 합니다.
ProviderScope(
child: MyApp(),
),
);
}
모든 프로바이더는 @riverpod 또는 @Riverpod()로 어노테이션해야 한다. 이 어노테이션은 전역 함수나 클래스에 배치할 수 있습니다.
예를 들어, @Riverpod(keepAlive: true)를 작성하여 "auto-dispose"를 비활성화할 수 있다.
@riverpod
Result myFunction(MyFunctionRef ref) {
<your logic here>
}
다른 providers와 상호작용하는 데 사용되는 객체다.
모든 providers에는 provider 함수의 매개변수(parameter) 또는 Notifier의 속성(property)으로 하나씩 가지고 있다. 이 객체의 타입은 함수/클래스의 이름에 의해 결정된다.
provider.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'activity.dart';
// 코드 생성이 작동하는 데 필요합니다.
part 'provider.g.dart';
/// 그러면 `activityProvider`라는 이름의 provider가 생성됩니다.
/// 이 함수의 결과를 캐시하는 공급자를 생성합니다.
@riverpod
Future<Activity> activity(ActivityRef ref) async {
// package:http를 사용하여 Bored API에서 임의의 Activity를 가져옵니다.
final response = await http.get(Uri.https('boredapi.com', '/api/activity'));
// 그런 다음 dart:convert를 사용하여 JSON 페이로드를 맵 데이터 구조로 디코딩합니다.
final json = jsonDecode(response.body) as Map<String, dynamic>;
// 마지막으로 맵을 Activity 인스턴스로 변환합니다.
return Activity.fromJson(json);
}
consumer.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'activity.dart';
import 'provider.dart';
/// The homepage of our application
class Home extends StatelessWidget {
const Home({super.key});
@override
Widget build(BuildContext context) {
return Consumer(
builder: (context, ref, child) {
// Read the activityProvider. This will start the network request
// if it wasn't already started.
// By using ref.watch, this widget will rebuild whenever the
// the activityProvider updates. This can happen when:
// - The response goes from "loading" to "data/error"
// - The request was refreshed
// - The result was modified locally (such as when performing side-effects)
// ...
final AsyncValue<Activity> activity = ref.watch(activityProvider);
return Center(
/// Since network-requests are asynchronous and can fail, we need to
/// handle both error and loading states. We can use pattern matching for this.
/// We could alternatively use `if (activity.isLoading) { ... } else if (...)`
child: switch (activity) {
AsyncData(:final value) => Text('Activity: ${value.activity}'),
AsyncError() => const Text('Oops, something unexpected happened'),
_ => const CircularProgressIndicator(),
},
);
},
);
}
}
@riverpod
Future<List<Todo>> todoList(TodoListRef ref) async {
// 네트워크 요청을 시뮬레이션합니다. 이는 일반적으로 실제 API로부터 수신됩니다.
return [
Todo(description: 'Learn Flutter', completed: true),
Todo(description: 'Learn Riverpod'),
];
}
@riverpod
class MyNotifier extends _$MyNotifier {
@override
Result build() {
<your logic here>
state = AsyncValue.data(42);
}
<your methods here>
}
코드 생성(code-generation)을 사용할 때 기본적으로 provider가 수신이 중지되면 상태가 파괴됩니다.
이는 리스너에 전체 프레임에 대한 활성 리스너가 없을 때 발생합니다. 이 경우 상태가 소멸됩니다.
이 동작은 keepAlive: true를 사용하여 해제(opted out)할 수 있습니다.
이렇게 하면 모든 리스너가 제거될 때 상태가 소멸되는 것을 방지할 수 있습니다.
// We can specify "keepAlive" in the annotation to disable
// the automatic state destruction
@Riverpod(keepAlive: true)
int example(ExampleRef ref) {
return 0;
}
@riverpod
Stream<int> example(ExampleRef ref) {
final controller = StreamController<int>();
// When the state is destroyed, we close the StreamController.
ref.onDispose(controller.close);
// TO-DO: Push some values in the StreamController
return controller.stream;
}
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return ElevatedButton(
onPressed: () {
// On click, destroy the provider.
ref.invalidate(someProvider);
},
child: const Text('dispose a provider'),
);
}
}
@riverpod
Future<String> example(ExampleRef ref) async {
final response = await http.get(Uri.parse('https://example.com'));
// We keep the provider alive only after the request has successfully completed.
// If the request failed (and threw), then when the provider stops being
// listened, the state will be destroyed.
ref.keepAlive();
// We can use the `link` to restore the auto-dispose behavior with:
// link.close();
return response.body;
}
extension CacheForExtension on AutoDisposeRef<Object?> {
/// Keeps the provider alive for [duration].
void cacheFor(Duration duration) {
// Immediately prevent the state from getting destroyed.
final link = keepAlive();
// After duration has elapsed, we re-enable automatic disposal.
final timer = Timer(duration, link.close);
// Optional: when the provider is recomputed (such as with ref.watch),
// we cancel the pending timer.
onDispose(timer.cancel);
}
}
@riverpod
Future<Object> example(ExampleRef ref) async {
/// Keeps the state alive for 5 minutes
ref.cacheFor(const Duration(minutes: 5));
return http.get(Uri.https('example.com'));
}