JINMUSOFTWARE

Riverpod 3 入門 一番簡単な説明

riverpod

Flutterで状態管理ツールといえばriverpodというパッケージがとても有名です。

簡単な使い方を説明します。

Riverpod 3 を少し使ってみましょう。

Riverpodとは

A Reactive Caching and Data-binding Framework

リアクティブ・キャッシングとデータバインディング・フレームワーク

https://riverpod.dev

状態管理ツールです。私はそう受け止めています。

  • グローバルから値を提供できます。
  • 深いWidgetでも提供された値を取得できます。
  • 提供されている値が変わるとWidgetが再描画されます。
  • Widgetから変更することも可能です。

使いたくなる場面

私の場合ですけど。

  • sqfliteを使用することになりました。
  • DBインスタンスは1つでいいし、グローバル的にアクセスできればいいかなと考えました。
  • そこでシングトンパターンを採用しました。
  • じゃあ、どこで初期化しようかなと考えます。
  • するとまぁやっぱり、App起動の最初のmain()関数かなとなります。そういう例もよく見ますね。
  • さて、初期化したdatabaseインスタンスを子孫Widgetに渡したいですよね。
  • 中継するすべてのWidgetのコンストラクタの引数にそれを追加しなければならなくなります。
  • そーです。引数のリレーが始まるのです。
  • これはとても面倒です。関係ないWidgetを修正したくありません。グローバルから飛び越えるように渡したいなと思ってしまいます。
  • Riverpod登場

環境

  • Riverpod 3.1.0
  • Flutter 3.38.3
  • Mac mini 2024 Apple M4

Riverpod 3 注意事項

riverpodには3種類のパッケージがあります。flutterで使用する場合は、「flutter_riverpod」を選びます。

  • riverpod
  • flutter_riverpod <- これです
  • hooks_riverpod

また、providerという名称のパッケージもあります。

riverpodにはproviderという名称の概念があります。providerパッケージとも勘違いしないように注意しましょう。

Riverpod 3 のProviderは6種類

提供できる値オブジェクトの種類に応じて、providerが6種類あります。

SynchronousFutureStream
UnmodifiableProviderFutureProviderStreamProvider
ModifiableNotifierProviderAsyncNotifierProviderStreamNotifierProvider

Notifier系は値を変更できます。(実際は新しいオブジェクトを再割り当てします。)

Legacyになったproviderもある

古くなったproviderもあります。

  • StateProvider
  • StateNotifierProvider
  • ChangeNotifierProviderです。

これら使用するときは新たにimportが必要になりました。「import ‘package:riverpod/legacy.dart’」です。

Providerを使ってみる

一番簡単。

同期的、変更メソッドなしのproviderです。

用途:値オブジェクトの提供です。遠くのWidgetまで届きます。

今回は単純な文字列を提供してみましょう。

Step概要

  • Install riverpod
  • Install riverpod lint
  • Import 追加
  • runAppにProviderScopeを挟む
  • 提供用Provider準備
  • ConsumerWidgetを継承して受信用Widgetを作成
  • ref.watchを利用して値オブジェクトを取得する。

Install

flutter_riverpod – pub.dev

pubspec.yamlに以下を追加します。

pubspec.yaml
dependencies:
  flutter_riverpod: ^3.1.0

コマンドラインで以下のように叩いてもよし。

command line
flutter pub add flutter_riverpod

lint有効化

analysis_options.yamlに以下を追加します。

analysis_options.yaml
analyzer:
  plugins:
    - riverpod_lint

plugins:
  riverpod_lint: ^3.1.0

VSCode拡張機能「Flutter Riverpod Snippets」

こちらも支援を受けられます。

Import

Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

ProviderScope設置

ここは補完が効くはず。

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

Provider

Topレベルに記述します。main()と同じレベルです。

Dart
final helloWorldProvider = Provider((_) => 'Hello');

中身は、「(_) => ‘Hello’」ですね。

文字列「Hello」です。これが値として提供されます。

ConsumerWidget

StatelessWidgetの代わりに、ConsumerWidgetを継承します。

build()メソッドの引数に「ref」が追加されています。

Dart
// ConsumerWidgetを継承します
class MyApp extends ConsumerWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    //値取得
    final String value = ref.watch(helloWorldProvider);

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Example')),
        body: Center(
          child: Column(
            children: [
              // 値表示
              Text(value),
            ],
          ),
        ),
      ),
    );
  }
}

ref.watchを利用して値オブジェクトを取得する

refを経由して、providerから値を取得できます。

Dart
final String value = ref.watch(helloWorldProvider);

取得後は適当にWidgetで使用します。

Dart
// 使用例
Text(value),

Code

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

// provider
final helloWorldProvider = Provider((_) => 'Hello');

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(colorScheme: .fromSeed(seedColor: Colors.deepPurple)),
      home: const MyHome(),
    );
  }
}

// ConsumerWidgetを継承します
class MyHome extends ConsumerWidget {
  const MyHome({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 値取得
    // providerを指定します
    final String value = ref.watch(helloWorldProvider);

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Example')),
        body: Center(
          child: Column(
            children: [
              // 値表示
              Text(value),
            ],
          ),
        ),
      ),
    );
  }
}

結果

providerから適当なwidgetへ値を提供できました。

Notifierを使う

さて、少し慣れてきましたね。

他のProviderも使ってみましょう。

更新機能のあるNotifierProviderを使ってみましょう。

Notifier

Notifierには、状態を表す”state”があります。これを操作するとwatchしているwidgetが再描画されます。

まずはコードから。「counter_provider.dart」を以下のように作成してみました。

counter_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

/// Notifier クラスの定義
class CounterNotifier extends Notifier<int> {
  /// これは初期値を返します。
  /// 型はジェネリクスTとも一致します。
  /// watch、readが初めて実行されたときにbuildが実行されるようです。lazyですね。
  @override
  int build() {
    return 0; // 初期値 // state が 0
  }

  void increment() {
    // stateへの操作で状態が更新されていく
    state++;
  }

  void decrement() {
    state--;
  }

  void reset() {
    state = 0;
  }
}

/// NotifierProvider の定義
/// Notifierを指定します。
final counterProvider = NotifierProvider<CounterNotifier, int>(
  //() => CounterNotifier(), // これでも良いがnewを使う方が一般的のようです。
  CounterNotifier.new,
);

無印なProviderとは違いが色々ありますね。

  • まず、Notifierクラスを継承したクラスを定義します。
  • 提供する値の型をジェネリクスで指定します。
  • stateの初期値はbuild()メソッドで初期化します。
  • stateを操作するメソッドを追加できます。
  • 今回は、このInt型の値を操作する、追加、削除、リセットのメソッドを追加しました。

Notifierができたら、NotifierProviderに登録します。

counter_provider.dart
final counterProvider = NotifierProvider<CounterNotifier, int>(
  CounterNotifier.new,
);

Consumer

次は、Providerから値を受け取る側を準備します。

今回はNotifierですので、更新処理もできるようにします。

ConsumerWidgetを継承したクラスを作成します。「counter_widget.dart」を次のように作成してみました。

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

// ConsumerWidgetを使ってProviderの値を監視する例
// 表示と更新を司るクラス
class CounterWidget extends ConsumerWidget {
  const CounterWidget({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // watchでProviderの値を監視する
    final count = ref.watch(counterProvider);

    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        // 表示ですね
        Text('Count: $count'),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            IconButton(
              icon: const Icon(Icons.remove),
              // readを使いnotifier経由でメソッドを叩く
              onPressed: () => ref.read(counterProvider.notifier).decrement(),
            ),
            IconButton(
              icon: const Icon(Icons.add),
              onPressed: () => ref.read(counterProvider.notifier).increment(),
            ),
            IconButton(
              icon: const Icon(Icons.refresh),
              onPressed: () => ref.read(counterProvider.notifier).reset(),
            ),
          ],
        ),
      ],
    );
  }
}
  • watchで状態を監視し値を取得します。状態に変更があればbuildが再実行されます。
  • 監視ではなく一回だけ取得するような場合はreadを使用します。notifierを指定し、メソッドを実行します。

main

mainはこんな感じにしてみました。

flutterのCounterアプリから余計なコードを落として、Riverpod的な部分を足しています。

StatefulWidgetが無くなっていますね。

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

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Riverpod Demo',
      theme: ThemeData(colorScheme: .fromSeed(seedColor: Colors.deepPurple)),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Riverpod Counter Example')),
      body: Center(child: const CounterWidget()),
    );
  }
}

結果

状態の監視、値の取得と更新、状態変化に反応して再描画が実装できましたね。

おわり

参考資料

Riverpod公式 – riverpod.dev

flutter_riverpod – pub.dev

Riverpod – GitHub

Riverpod lint – pub.dev