55
43

More than 3 years have passed since last update.

【Flutter】Riverpod の主要な provider 一覧

Posted at

はじめに

Flutter の状態管理手法は数多くあります。
そして最近では新たに Riverpod というパッケージが登場しました。Riverpod は Provider パッケージと同じ方によるもので、("Riverpod" は "Provider" のアナグラムになっている。すごい!)状態管理手法に新たな選択肢を提示します。
筆者もこの Riverpod を使ってみようと思ったのですが、Riverpod 内の provider にもいくつか種類があり、何があるのか最初よくわからなかったのでこの記事にまとめてみます。
なお、筆者は Riverpod に全然詳しくなく、Provider についてもあまり使いこなせているわけではないので、内容に誤りがあったり改善点などがございましたら是非ご指摘ください。(むしろアドバイスや意見が欲しくて記事を書いてるまであります。)

Provider

Provider は最も基本的な provider だと思います。

final cityProvider = Provider((ref) => 'London');

class CitySample extends ConsumerWidget {

  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final city = watch(cityProvider);

    return Scaffold(
      appBar: AppBar(
        title: Text('City Sample'),
      ),
      body: Center(
        child: Text('I want to visit $city.'),
      ),
    );
  }
}

といった感じで定義して使用します。筆者の理解では Provider は基本的に後から値を変えることができないので、読み取り専用のデータを渡すために使っています。

StateProvider

StateProvider もよく使う provider だと思います。
int や String など、シンプルな値を管理するための provider です。
より複雑な値(複数の値をまとめたオブジェクトなど)を管理したい場合は次の StateNotifierProvider を使ったほうが良さそうです。
Provider との違いは、状態の書き換えが可能なことだと考えています。

final countProvider = StateProvider<int>((ref) => 0);

class CountSample extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final count = watch(countProvider);

    return Scaffold(
      appBar: AppBar(
        title: Text('Count Sample'),
      ),
      body: Center(
        child: Text('Tapped ${count.state} times.'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          count.state++;
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

といった感じで使用します。(筆者は現状、flutter_riverpod を使っているので ConsumerWidget を継承していますが、hooks_riverpod を使っている場合は若干書き方が変わるようです。)

StateNotifierProvider

StateNotifierProvider は名前の通り、StateNotifier を渡す provider です。そもそも StateNotifier とは何かと思う方もいるかもしれませんが、これについては同作者による同名のパッケージが独立して存在しており、それについての記事もいくつか上がっているのでそちらをご参照ください。

StateNotifierを使ったFlutterのアプリ設計
Flutter state_notifierいい感じなので使ったほうが良いですよ
state_notifier と freezed を使って、Flutterのカウンターアプリをつくるよ

使い方は大体 StateProvider と同じなのですが、いくつか違う点もあります。

// 普通はもっと複雑になるとおもうので freezed パッケージを使う。
class CounterState {
  CounterState(this.count);
  final int count;
}

class Counter extends StateNotifier<CounterState> {
  Counter(CounterState state) : super(state);
  increment() {
    state = CounterState(state.count + 1);
  }
}

final counterProvider =
    StateNotifierProvider<Counter>((ref) => Counter(CounterState(0)));

class CounterSample extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final counter = watch(counterProvider.state);

    return Scaffold(
      appBar: AppBar(
        title: Text('Counter Sample'),
      ),
      body: Center(
        child: Text('Tapped ${counter.count} times.'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          context.read(counterProvider).increment();
        },
        tooltip: 'Inclement',
        child: Icon(Icons.add),
      ),
    );
  }
}

まあ大体こんな感じだと思います。
今回の例は、管理する値が一つしかないので全く StateNotifier を使う意味がありませんが、この値がたくさんある場合は有効になってくるはずです。(1つしかない場合はStateProvider を使う。)またそういう場合は同作者の freezed パッケージを使うことが推奨されるようです。

FutureProvider

FutureProvider は、値を得るのに時間がかかる場合(HTTPリクエストやローカルファイルの読み込みなど)に使うようです。少し特殊なのが、値が AsyncValue という Riverpod 独自のオブジェクトで返される点です。
AsyncValue には when というメソッドがあり、これによってデータが得られた時、ローディング中、エラー発生時で処理を分けることができます。これは状態によって表示を分けたい場合非常に便利です。

final cityFutureProvider = FutureProvider<String>((ref) {
  return Future.delayed(Duration(seconds: 5), () => 'Paris');
});

class CityFutureSample extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final city = watch(cityFutureProvider);

    return Scaffold(
      appBar: AppBar(
        title: Text('City Future Sample'),
      ),
      body: Center(
        child: city.when(
            data: (data) => Text('You want to visit $data.'),
            loading: () => CircularProgressIndicator(),
            error: (error, stack) => Text('Error!')),
      ),
    );
  }
}

また、他の Provider から FutureProvider の値を使いたい場合は単純に次のようにしたら良いと思います。(たぶん)

final cityFutureProvider = FutureProvider<String>((ref) {
  return Future.delayed(Duration(seconds: 5), () => 'Paris');
});

final textProvider = Provider<String>((ref) {
  return ref.watch(cityFutureProvider).when(
      data: (data) => 'You want to visit $data.',
      loading: () => 'Now loading...',
      error: (error, stack) => 'Error!');
});

StreamProvider

StreamProvider は FutureProvider のストリーム版です。
これも AsyncValue を返します。
大体 FutureProvider と同じだと思うので例は割愛。

ScopedProvider

ScopedProvider は少し特殊です。最初は null を返すものとして定義して、後から
上位の ProviderScope で値を上書きすることができます。

final cityFutureProvider = FutureProvider<String>((ref) {
  return Future.delayed(Duration(seconds: 5), () => 'Paris');
});

class CityScopedSample extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final city = watch(cityFutureProvider);

    return Scaffold(
      appBar: AppBar(
        title: Text('City Scoped Sample'),
      ),
      body: Center(
        child: city.when(
            data: (data) => ProviderScope(
                overrides: [cityProvider.overrideWithValue(data)],
                child: _CityScopedSample()),
            loading: () => CircularProgressIndicator(),
            error: (error, stack) => Text('Error!')),
      ),
    );
  }
}

final cityProvider = ScopedProvider<String>((ref) => null);

class _CityScopedSample extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final city = watch(cityProvider);

    return Text('You want to visit $city.');
  }
}

コンストラクタに渡さずに下位に値を与えることができます。
ただし、ScopedProvider の値を参照できるのはUIのみで、別の provider から ScopedProvider の値を利用したりはできないので注意が必要です。

今回は、FutureProvider から得られた値で cityProvider の初期値を上書きしています。(態々こんなことするより直接 AsyncValue を使ったほうが良さそう。)
正直、イマイチ使いみちがわかっていません。

また、この ProviderScope による値の上書きですが、ScopedProvider 以外の provider の値を上書きする場合には、最上位の ProviderScope で行わないとエラーになるようです。
Unsupported operation: Cannot override providers on a non-root ProviderContainer/ProviderScope
と言われます。

.family

各 provider は .family を使用することができ、(ScopedProvider 除く)これによって引数をもとに動的に provider を生成することができます。

//定義
final familyProvider = Provider.family<'戻り値の型', '引数の型'>((ref, '引数'){});

//参照
ref.watch(familyProvider('引数'));

みたいな感じで使えます。

よくわからん例ですが、下のようなことができます。

enum Member {
  father,
  mother,
  son,
  daughter,
}

final familyAgeProvider = Provider.family<int, Member>((ref, member) {
  switch (member) {
    case Member.father:
      return 40;
    case Member.mother:
      return 35;
    case Member.son:
      return 10;
    case Member.daughter:
      return 5;
    default:
      return 0;
  }
});

class FamilySample extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final children = List<Widget>();
    Member.values.forEach((member) {
      final name = member.toString().split('.').last;
      final age = watch(familyAgeProvider(member));
      children.add(Text('The $name is $age years old.'));
    });

    return Scaffold(
      appBar: AppBar(
        title: Text('City Sample'),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: children,
        ),
      ),
    );
  }
}

また、他に .autoDispose というものもあり、これを使うともう必要なくなったときに自動的に状態を破棄してくれるようです。( .autoDispose と .family は併用できる。)

おわりに

最後までお読みいただきありがとうございました。
ここまでが、実際に使いそうな provider のまとめとなります。
Riverpod の情報は(特に日本語だと)まだまだ少ないので、この記事がこれから Riverpod を使おうとしてる方の役に立てば幸いです。

なお Riverpod はまだまだ発展途上のパッケージなので、今後仕様が変わったり、provider が増えたり減ったりするかもしれません。また、筆者もまだ使い始めたばかりですので、間違ってる情報があるかもしれません。
なので必ず API reference をご確認ください。

参考

Riverpod を使い始める際に参考になったページです。

Riverpod
Flutterの状態管理手法の選定
[Flutter]Riverpodを使ってToDoアプリを作ってみた
RiverpodとFlutter Hooksを使う、はじめの一歩
【神パッケージ】 Riverpod の使い方【Flutter】
【Flutter】Providerのほぼ上位互換、Riverpodの基本的な使い方

55
43
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
55
43