Riverpodを使ってみる

2023年4月30日

開発環境
OS:Windows 11
SDK:VS Code + flutter 3.7.5

パッケージとは

Flutterで実装するうえでかかせないのがパッケージです。

Flutterのベース機能はUIに特化されているため、アプリの機能性、操作性を拡張させるような実装を行うにはパッケージをインストールする必要があります。

Webを表示させたい、画像を表示させたい、データ管理を行いたい、など、

ベースロジックで対応しきれない場合は、Pub.devから適切なパッケージを探し、インストールします。

今回は状態管理を行うパッケージ、Riverpodを利用してアプリを作ります。

パッケージをインストールする

インストール手順は簡単です。

コマンドプロンプト(ターミナル)でパッケージ(flutter_riverpod)をインストールしたい当該プロジェクトフォルダに移動し、下記コマンドを実行します。

flutter pub add flutter_riverpod

プロジェクトにパッケージがインストールされます。

パッケージはpubspec.yamlで管理されており、yamlファイル内に指定したパッケージが追加されます。

逆にpubspec.yamlにインストールしたいパッケージを追記し、下記コマンドを実行してもパッケージをインストールすることができます。

flutter pub get

実装してみる

flutter create test2で作成したサンプルプログラムをRiverpodで置き換えてみます。

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

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

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(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends ConsumerWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  void _incrementCounter(WidgetRef ref) {
    ref.read(countProvider.notifier).state++;
  }

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              ref.watch(countProvider).toString(),
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {_incrementCounter(ref);},
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

まずパッケージを使用するためにパッケージをimportします。

Riverpodを適用するためのポイントは3点です。

1)ProviderScopeで適用範囲とするクラスを指定する

2)StatefulWidget → CunsumerWidget へ置き換える

3)buildの引数にWidgetRefを追加する

あとはStateProviderを定義して参照、更新します。

値を参照する ref.read(定義名).state

値を参照する(更新検知) ref.watch(定義名).state

値を更新する ref.read(定義名.notifier).state = 更新値 

実行すると下記のようになりました。

setStateを使用したサンプルプログラムと同じ動きになります。

またRiverpodの優れた点は別ページに遷移しても変わらず参照できる点です。

下記のようにページ遷移を実装します。

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:test2/pages/nextpage.dart';

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

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(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends ConsumerWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  void _incrementCounter(WidgetRef ref) {
    ref.read(countProvider.notifier).state++;
  }

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              ref.watch(countProvider).toString(),
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            ElevatedButton(onPressed: (){Navigator.push(context, MaterialPageRoute(builder: (context) => const NextPage()));}, child: const Text('next'))
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {_incrementCounter(ref);},
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

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

class NextPage extends ConsumerWidget {
  const NextPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("next page"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times on Main page:',
            ),
            Text(
              ref.watch(countProvider).toString(),
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),// This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

実行すると下記となります。

メイン画面
次画面

次ページにおいてもメイン画面の値を参照できます。

ちなみに複雑な状態管理を行うにはStateNotifierProviderを使用します。

今回は以上です。