11
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-06-21

この記事は昨日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を使ったカウンターアプリです。

  • ProviderStateNotifierProviderを監視して、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),
      ),
    );
  }
}

11
7
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
11
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?