【Flutter】Riverpodで使うProviderの種類をわかりやすくまとめてみた

Riverpodの各Providerについて、用途とサンプルコードをまとめた学習メモ。

Markdownで開く

過去の記事(Qiitaから移行)

この記事は公開当時の知識・環境に基づいて書いたものです。現在の仕様に合わせた更新・動作確認は行っておらず、内容の正確性や動作を保証するものではありません。

初出:2022-06-21・Qiita

注意

この記事は昨日Riverpodを触った人がノート感覚でまとめました。 本当に吸収が早い学生だと思っているので、求人オファーお待ちしてます。

Provider

https://riverpod.dev/ja/docs/providers/provider/ Provider はプロバイダの中で最もベーシックなプロバイダであり、値を同期的に生成してくれます。 例

/// countProviderの値を2倍にする。
final doubleCountProvider = Provider<int>((ref) {
  final count = ref.watch(countProvider);
  return count * 2;
});

//値を読み込む
int doubleCount = ref.watch(doubleCountProvider);

この例では、countProviderというプロバイダーを監視し、2倍の値を返すものです。 値を代入することはできません。つまり値を監視して、適当な処理をした値を返す用途として使えます。 他にも、キャッシュとして利用することもできます。 公式のキャッシュとしてProviderを利用する例

StateProvider

https://riverpod.dev/ja/docs/providers/state_provider StateProvider は外部から変更が可能なステート(状態)を公開するプロバイダです。 例

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

//値を読み込む
int countState = ref.watch(countStateProvider);

//書き込む時(パターン1)
ref.read(countStateProvider.notifier).state = 1;

//書き込む時(パターン2)
ref.read(countStateProvider.notifier).update((state) => state + 1);

この例では、int型のproviderを定義し、初期値は0となっています。 好きな型をRiverpodで扱えるというものです。後述するStateNotifierProviderは関数を定義するなどして、細かいことができますが、こちらは反対で普通の変数を扱うような感覚で利用できます。

書き込みはupdateを使用することで、現在の値を元に新しい値を決めることができます。

StateNotifierProvider

https://riverpod.dev/ja/docs/providers/state_notifier_provider StateNotifierProvider は StateNotifier(Riverpod が依存する state_notifier パッケージのクラス)を監視し、公開するためのプロバイダです。

後述するStateNotifierを扱うためのProviderです。前述のStateProviderが変数を管理するものでしたが、こちらはクラスを管理することができます。StateNotifierの説明をしてから詳しく説明します

StateNotifier

StateNotifierProviderが扱えるProviderです。クラスとして定義するので、メゾットなどを用いて幅広い使い方ができます。 例

class CountStateNotifier extends StateNotifier<int> {
  CountStateNotifier() : super(0);

  void increment() {
    state++;
  }
}

この例では、int型のStateを作成し、初期値に0を指定しています。 そして、incrementを呼び出すことにより、値を1増加させます。

StateNotifierProvider(続き)

例

final countStateNotifierProvider =
    StateNotifierProvider<CountStateNotifier, int>((ref) {
  return CountStateNotifier();
});

//値を読み込む
int countStateNotifier = ref.watch(countStateNotifierProvider);

//メゾットを呼び出す
ref.read(countStateNotifierProvider.notifier).increment();

StateNotifierを監視するためのProviderを作成しています。<>の左側には利用したいStateNotifierを、右側には返したい型を指定します。

FutureProvider

https://riverpod.dev/ja/docs/providers/future_provider FutureProvider は非同期操作が可能な Provider であると言えます 例

/// 5秒後に経過しました!が非同期で返される。
final countFutureProvider = FutureProvider<String>((ref) async {
  await Future.delayed(const Duration(seconds: 5));
  return "5秒経過しました!";
});

// 値を読み込む
AsyncValue<String> countFuture = ref.watch(countFutureProvider);

// Widgetとして利用してみる
countFuture.when(
    loading: () => const Text("Loading"),
    error: (_, __) => const Text("Error"),
    data: (String data) {
      return Text(data);
    })

この例はは、lateFutureProviderが5秒後に5秒経過しました!となるプログラムです。 非同期なものを使いたい時はこのProviderを使用することになるでしょう。

StreamProvider

https://riverpod.dev/ja/docs/providers/stream_provider StreamProvider は FutureProvider の Stream 版です。 例

///1秒ごとにランダムな値を出すStream
Stream<int> randomValueStream() async*{
  final random = Random();
  while(true){
    await Future.delayed(const Duration(seconds: 1));
    yield random.nextInt(100);
  }
}

//Providerを作成
final countStreamProvider = StreamProvider<int>((ref) => randomValueStream());

//値を読み込む
AsyncValue<int> countStream = ref.watch(countStreamProvider);

FutureProviderのStreamバージョンです。 FirebaseやWebhookなど、リアルタイムで変化する値がある際に使用します。

ChangeNotifierProvider

https://riverpod.dev/ja/docs/providers/change_notifier_provider ChangeNotifier を Flutter で利用するためのプロバイダです。

重要

公式が非推奨としています。

例

// 使用するChangeNotifier
class CountChangeNotifier extends ChangeNotifier {
  int count = 0;

  void increment() {
    count ++;
  }
}

//ChangeNotifierProviderを作成
final countChangeNotifierProvider =
    ChangeNotifierProvider<CountChangeNotifier>((ref) {
  return CountChangeNotifier();
});

//読み込む
int countChangeNotifier = ref.watch(countChangeNotifierProvider).count;

//値を変更(1)
ref.read(countChangeNotifierProvider).increment();
//値を変更(2)
ref.read(countChangeNotifierProvider).count++;

はい。StateNotifierProviderにとても似ていますが、これまでと異なりProvider自体が型を管理していません。 基本的には使わないことをおすすめします。

まとめ

  • Provider(公式)
    • 代入不可(決めた処理に沿った値が出力される)
    • 他のProviderの値を処理してから出力するなどの用途で使える。
    • 一時的なキャッシュとして利用することでパフォーマンスも向上できる。
  • StateProvider(公式)
    • 代入可能
    • 変数のように利用したい時に便利。シンプル。
  • StateNotifierProvider(公式)
    • メゾット経由で更新できる。
    • StateNotifierで指定した値を出力する。
    • メゾット等を持たせることができるので、可変なものを管理するのに便利。
  • FutureProvider(公式)
    • 代入不可
    • 非同期なもので利用すると便利。
    • watchすると、AsyncValueが返ってくる。
  • StreamProvider(公式)
    • 代入不可
    • FutureProviderのStream版
    • FirebaseやWebSocket等で使うととても便利。
    • watchすると、AsyncValueが返ってくる。
  • ChangeNotifierProvider(非推奨)(公式)
    • 代入とメゾット経由の更新が両方できる。
    • watchすると、ChangeNotifierが返ってくる。
    • 可能な限り、StateNotifierProviderを使おう。

よろしければLGTMお願いします!!

サンプルコード

今回利用した6つのProviderを使ったカウンターアプリです。

  • ProviderはStateNotifierProviderを監視して、2倍の値を表示します。
  • FutureProviderは5秒後に5秒経過しました!と表示します。
  • StreamProviderは1秒ごとに0-100の乱数を表示します。

IMG_72C035C9DCA9-1.jpeg

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

///Provider
final doubleCountProvider = Provider<int>((ref) {
  final count = ref.watch(countStateNotifierProvider);
  return count * 2;
});

///StateProvider
final countStateProvider = StateProvider<int>((ref) => 0);

///StateNotifierProvider
class CountStateNotifier extends StateNotifier<int> {
  CountStateNotifier() : super(0);

  void increment() => state++;
}

final countStateNotifierProvider =
    StateNotifierProvider<CountStateNotifier, int>((ref) {
  return CountStateNotifier();
});

///FutureProvider
final countFutureProvider = FutureProvider<String>((ref) async {
  await Future.delayed(const Duration(seconds: 5));
  return "5秒経過しました!";
});

///StreamProvider
Stream<int> randomValueStream() async* {
  final random = Random();
  while (true) {
    await Future.delayed(const Duration(seconds: 1));
    yield random.nextInt(100);
  }
}

final countStreamProvider = StreamProvider<int>((ref) => randomValueStream());

///ChangeNotifierProvider
class CountChangeNotifier extends ChangeNotifier {
  int count = 0;

  void increment() => count++;
}

final countChangeNotifierProvider =
    ChangeNotifierProvider<CountChangeNotifier>((ref) {
  return CountChangeNotifier();
});

class MyHomePage extends ConsumerWidget {
  final String title;

  const MyHomePage({Key? key, required this.title}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    //Provider
    int doubleCount = ref.watch(doubleCountProvider);

    //StateProvider
    int countState = ref.watch(countStateProvider);

    //StateNotifierProvider,StateNotifier
    int countStateNotifier = ref.watch(countStateNotifierProvider);

    //FutureProvider
    AsyncValue<String> countFuture = ref.watch(countFutureProvider);

    //SteamProvider
    AsyncValue<int> countStream = ref.watch(countStreamProvider);

    //ChangeNotifierProvider
    int countChangeNotifier = ref.watch(countChangeNotifierProvider).count;

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Table(
          columnWidths: const <int, TableColumnWidth>{
            0: FlexColumnWidth(0.5),
            1: FlexColumnWidth(0.5),
          },
          children: [
            TableRow(children: [
              const Text("Provider"),
              Text("$doubleCount"),
            ]),
            TableRow(children: [
              const Text("StateProvider"),
              Text("$countState"),
            ]),
            TableRow(children: [
              const Text("StateNotifierProvider"),
              Text("$countStateNotifier"),
            ]),
            TableRow(children: [
              const Text("FutureProvider"),
              Text(countFuture.when(
                  data: (String data) => data,
                  error: (_, __) => "error",
                  loading: () => "loading")),
            ]),
            TableRow(children: [
              const Text("StreamProvider"),
              Text(countStream.when(
                  data: (int data) => "$data",
                  error: (_, __) => "error",
                  loading: () => "loading")),
            ]),
            TableRow(children: [
              const Text("ChangeNotifierProvider"),
              Text("$countChangeNotifier"),
            ]),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          ref.read(countStateNotifierProvider.notifier).increment();
          ref.read(countStateProvider.notifier).update((state) => state + 1);
          ref.read(countChangeNotifierProvider).count++;
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}
← 記録一覧へ戻る