Skip to content

4. Riverpod の導入

4.1 Riverpodのインストール

まずはflutter_riverpod をプロジェクトに追加します。

sh
flutter pub add flutter_riverpod
flutter pub add flutter_riverpod

4.2 Provider の作成

まず、盤面の状態を管理するためのProviderを作成します。

libディレクトリ配下にproviderディレクトリを、さらにその配下にtic_tac_toe_provider.dartを作成します。

bash
🗂 lib
    🗂 model                          : 各種データモデル
      - 📄 tic_tac_toe.dart            : 三目並べのデータモデル
    🗂 provider                       : 各種プロバイダー
      - 📄 tic_tac_toe_provider.dart   : 三目並べのプロバイダー
    🗂 view                           : 各種ページビュー
      - 📄 board.dart                  : 三目並べのページビュー
   - 📄 main.dart                      : アプリケーションのエントリーファイル
- 📄 pubspec.yaml                      : アプリケーションで使う依存関係の設定
🗂 lib
    🗂 model                          : 各種データモデル
      - 📄 tic_tac_toe.dart            : 三目並べのデータモデル
    🗂 provider                       : 各種プロバイダー
      - 📄 tic_tac_toe_provider.dart   : 三目並べのプロバイダー
    🗂 view                           : 各種ページビュー
      - 📄 board.dart                  : 三目並べのページビュー
   - 📄 main.dart                      : アプリケーションのエントリーファイル
- 📄 pubspec.yaml                      : アプリケーションで使う依存関係の設定

tic_tac_toe_provider.dart作成手順は下記の通り進めましょう。

  1. プロジェクトのlibディレクトリの中に、新しくproviderというディレクトリを作成します。
  2. その中にtic_tac_toe_provider.dartというファイルを作成します。
  3. 以下のコードをファイルに書き込みます。
dart
// lib/provider/tic_tac_toe_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:tic_tac_toe_handson/model/tic_tac_toe.dart';

final ticTacToeProvider = StateProvider<TicTacToe>((ref) {
  return TicTacToe.start();
});
// lib/provider/tic_tac_toe_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:tic_tac_toe_handson/model/tic_tac_toe.dart';

final ticTacToeProvider = StateProvider<TicTacToe>((ref) {
  return TicTacToe.start();
});

StateProviderを使うことでProviderで保持する盤面の状態を変更することができます。初期状態はTicTacToe.start()で設定しておきます。

ちなみに、Providerに.autoDispose修飾子をつけると、そのProviderがアプリケーションのどこからも使われなくなった際にRiverpod側で自動でデータを破棄してくれます。特に理由がない限りは.autoDisposeをつけておくことをお勧めします。ちなみに、AutoDisposeStateProviderというProviderを使っても同様のことを実現できます。

dart
StateProvider.autoDispose
StateProvider.autoDispose

4.3 状態変化に応じて UI を更新する

次にUIに関するコードを変更していきます。

4.3.1 main.dartの更新

Riverpodをアプリケーション内で利用するには、ProviderScopeでラップする必要があります。 Widget内でProviderの値を読み取れるようにしておくためには、アプリケーション全体をProviderScopeでラップする必要があります。

dart
// lib/main.dart
void main() {
  runApp(
    const ProviderScope(
      child: MyApp(),
    ),
  );
}
// lib/main.dart
void main() {
  runApp(
    const ProviderScope(
      child: MyApp(),
    ),
  );
}

4.3.2 view/board.dartの更新

Widget内でproviderにアクセスするにはConsumerWidgetを継承します。

ConsumerWidgetでは、buildメソッドの第二引数にWidgetRef refを追加されます。これを使ってProviderにアクセスすることができ、ref.watchでproviderで保持している状態の値を読み取ることができます。また、provider内の状態が変わるたびにConsumerWidgetが再描画されるようになります。

dart
// lib/view/board.dart
class Board extends ConsumerWidget {
  const Board({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final ticTacToe = ref.watch(ticTacToeProvider);

    // ...略...
  }
}
// lib/view/board.dart
class Board extends ConsumerWidget {
  const Board({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final ticTacToe = ref.watch(ticTacToeProvider);

    // ...略...
  }
}

refにはref.watchref.readの2種類の主な使い方があります。ref.watchはproviderの状態が変わるたびに再描画が走りますが、ref.readはその時の値を一度だけ読み取るだけです。

4.3.3 状態の更新方法

最後に状態の更新処理を変更していきます。ref.read(ticTacToeProvider.notifier).stateでproviderの状態にアクセスし、それに対して値を代入することで状態を更新しています。

マークを配置する処理は以下のようになります。

dart
return GestureDetector(
  onTap: () {
    final winner = ticTacToe.getWinner();
    if (mark.isEmpty && winner.isEmpty) {
      ref.read(ticTacToeProvider.notifier).state = ticTacToe.placeMark(row, col);
    }
  },
  // ...略...
)
return GestureDetector(
  onTap: () {
    final winner = ticTacToe.getWinner();
    if (mark.isEmpty && winner.isEmpty) {
      ref.read(ticTacToeProvider.notifier).state = ticTacToe.placeMark(row, col);
    }
  },
  // ...略...
)

また、ゲームのリセットは以下のようになります。

dart
ElevatedButton(
  onPressed: () {
    ref.read(ticTacToeProvider.notifier).state = ticTacToe.resetBoard();
  },
  child: const Text('ゲームをリセット'),
)
ElevatedButton(
  onPressed: () {
    ref.read(ticTacToeProvider.notifier).state = ticTacToe.resetBoard();
  },
  child: const Text('ゲームをリセット'),
)

これで状態の管理と盤面のUI更新をRiverpodを使って行うことができるようになりました。

コントリビューター

okaryo

okaryo

現在は主にRubyやGoを仕事で扱っていますが、今もFlutterは大好きです!