Flutter riverpod
状態管理のためのライブラリ
Providerの置き換えを狙ったもの
Providerは内部ではInheritedWidgetを使っていたが、riverpodは独自実装
Flutterの外側でも使える
hello world
code:main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// We create a "provider", which will store a value (here "Hello world").
// By using a provider, this allows us to mock/override the value exposed.
// グローバルにProviderを作る
// これはProvider libraryとは別のもの
final helloWorldProvider = Provider((_) => 'Hello world');
void main() {
runApp(
// For widgets to be able to read providers, we need to wrap the entire
// application in a "ProviderScope" widget.
// This is where the state of our providers will be stored.
// グローバルに宣言したProviderを使うには、ProviderScopeウィジェットが必要
ProviderScope(
child: MyApp(),
),
);
}
// Extend ConsumerWidget instead of StatelessWidget, which is exposed by Riverpod
// ConsumerWidgetを継承してウィジェットを作る
// これはStatelessWidgetの代わりに使える
// 内部では、watchやreadでProviderにアクセスできる
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),
),
),
);
}
}
concepts
provider
Providerはriverpodで最も重要な要素
Providerは状態をカプセル化して、それを購読できるようにするオブジェクト
Provider libraryにおいてはProviderはウィジェットだったが、riverpodでは違う
名前がややこしいw
creating a provider
code:dart
final myProvider = Provider((ref) {
return MyValue();
});
グローバルに宣言する
Providerはイミュータブルなのでそこまで神経質になる必要はないらしい(ほんと?
Provider以外にも、StreamProviderやStateNotifierProviderが使える
Providerに渡した関数は、常にrefと呼ばれるオブジェクトを受け取る
このオブジェクトは、他のproviderを読み取ったり、providerの状態が消える時に何かをする時に使う
Providerに渡す関数は基本的に何を返しても良い
<T>で型を指定もできる
StreamProviderは例外的に、Streamを返すことを想定している
Provider libraryでは同じ型の値を流す事はできない(最初に見つけた型を返してしまう)が、Riverpodでは同じ型でもOK
code:dart
final cityProvider = Provider((ref) => 'London');
final countryProvider = Provider((ref) => 'England');
Performing actions before the state destruction
Providerから流している状態を一度消して、リセットしたい場合もある
そんなときは、Providerの状態を消す前に、クリーンアップを行うのが一般的
例えば、StreamControllerを閉じるとか
これはrefを使う
code:dart
final example = StreamProvider.autoDispose((ref) {
final streamController = StreamController<int>();
ref.onDispose(() {
streamController.close();
});
return streamController.stream;
});
Provider modifiers
autoDispose familyが使える
code:dart
final myAutoDisposeProvider = StateProvider.autoDispose<String>((ref) => "0");
final myFamilyProvider = Provider.family<String, int>((ref, id) => '$id');
autoDispose
Providerから渡している状態が誰も購読しなくなったら、自動で破棄する
family
providerを外部のパラメータから作れるようにする
これだけだとなんともわからん
ちなみに両方を同時に使える
Provider.autoDispose.family
Reading Provider
いろんな購読の仕方がある
これに従うのが一番早そうではある
https://riverpod.dev/assets/images/reading-e6984a41c05abc4421f8fb155f7c5ff3.svg
ウィジェット内部
ビルドメソッド内
Consumer
それ以外ではcontext.read
イベントハンドラ系
Provider packageと大差なし
例:
userProviderを購読するとする
code:dart
final userProvider = StreamProvider<User>(...);
同期的に状態を取得したければ、
code:dart
// ConsumerWidgetを継承したウィジェット
Widget build(BuildContext context, ScopedReader watch) {
AsyncValue<User> user = watch(userProvider);
return user.when(
loading: () => const CircularProgressIndicator(),
error: (error, stack_ => const Text('Oops'),
data: (user) => Text(user.name),
);
}
Providerに紐づいたStreamを手に入れたければ、userProvider.stream
code:dart
Widget build(BuildContext context, ScopedReader watch) {
Stream<User> user = watch(userProvider.stream);
}
Streamの直近の値をFutureで取りたければ、userProvider.last
code:dart
Widget build(BuildContext context, ScopedReader watch) {
Future<User> user = watch(userProvider.last);
}
Consuming a provider inside widgets
ConsumerWidgetを使う
code:dart
class Home extends ConsumerWidget {
@override
Widget build(BuildContext context, ScopeReader watch) {
// Listens to the value exposed by counterProvider
int count = watch(counterProvider).state;
return Scaffold(
appBar: AppBar(title: const Text('counter sample')),
body: Center(
child: Text('$count'),
),
);
}
}
あるいは、Consumerウィジェットを使う
code:dart
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Counter example')),
body: Center(
child: Consumer(
// Rebuild only the Text when counterProvider updates
builder: (context, watch, child) {
// Listens to the value exposed by counterProvider
int count = watch(counterProvider).state;
return Text('$count');
},
),
),
);
}
}
ProviderListener
providerで何か変化があった後に、ダイアログを出したい時
code:dart
Widget build(BuildContext context) {
return ProviderListener<StateController<int>>(
provider: counterProvider,
onChange: (context, counter) {
if (counter.state == 5) {
showDialog(...);
}
},
child: Whatever(),
);
}
Reading a provider inside another provider
例えばProviderでオブジェクトを他のオブジェクトから作りたい時
UserControllerをUserRepositoryから作る例を考える
両方のオブジェクトが、それぞれ別のProviderから配信されるケース
refを使うことで可能になる
code:dart
final userRepositoryProvider = Provider(ref) => UserRepository());
final userControllerProvider = StateNotifierProvider((ref) {
return UserController(
repository: ref.watch(userRepositoryProvider),
);
});
Combining providers
機能が増えてくると、だいたい別のProviderで管理されている値を、Providerで取りたくなる
例えばログイン状態とか
ref.watchを使うことで可能になる
code:dart
final cityProvider = Provider((ref) => 'London');
final weatherProvider = FutureProvider((ref) async {
final city = ref.watch(cityProvider);
return fetchWeather(city: city);
});
What if the value listened changes over time?
StateNotifierProviderを購読することを考える
code:dart
class TodoList extends StateNotifier<List<Todo>> {
//空っぽのTodoListを初期値として持つ
TodoList(): super(const []);
}
// ProviderではTodoListを配信する
final todoListProvider = StateNotifierProvider((ref) => TodoList());
上記の例では、UIがTodoリストをフィルタする
completed / uncompletedなTodoのみを表示する、とか
code:dart
enum fliter {
none,
completed,
uncompleted,
}
// StateProviderで、Filterの状態を配信する
final filterProvider = StateProvider((ref) => Filter.none);
2つのProviderを結合する
code:dart
final filteredTodoListProvider = Provider<List<Todo>>((ref) {
final filter = ref.watch(filterProvider);
final todos = ref.watch(todoListProvider);
switch(filter.state) {
case Filter.none:
return todos;
case Filter.completed:
return todos.where((todo) => todo.completed).toList();
case Filter.uncompleted:
return todos.where((todo) => !todo.completed).toList();
}
});
シンプルなStateNotifierをたくさん作って、状況に応じてCombineさせることで複数の状態を扱いやすくなる
2021/6/23
v1.0がリリースされていた
まあまあBreaking Changesがある