工作と競馬2

電子工作、プログラミング、木工といった工作の記録記事、競馬に関する考察記事を掲載するブログ

Flutter Riverpod基礎

概要

Flutterで、Riverpodを使うための基礎的なことの自分用メモ。




背景と目的

Flutterでは、状態管理の実現方法はいろいろあるが、

  • ビュー、ロジック、ステートの分離
  • ステータス変数へのグローバルなアクセス

を実現する方法として最も用いられていると思われるRiverpodを使いたい。そこで、Riverpodを使用するための基礎的なことの自分用にメモする。



詳細

0. インストール

flutter pub add flutter_riverpod


1. 前提を整理

説明のため、以下を前提とする。

  • MyWidget: ボタンとテキストを持つWidgetクラス
    • インクリメントというボタンを押したら、上部のテキスト内の数値が増える
  • MyWidgetState: 状態を管理するクラス
    • counterという変数を持つ
  • MyWidgetStateNotifier: ロジックを管理するStateNotifierクラス
    • counterを変化させるincrementというメソッドを持つ


1. フォルダ分け

ビュー、ステート、ロジックをそれぞれ分離してフォルダ分けすることが一般的らしいので、MyWidgetに関連する一連のファイルは、以下の構造で格納する。

  • screen: ビュー=見た目を定義する
  • state: 状態を定義する
  • view_model: 状態を変化させるロジックを定義する
lib/
  screen/
    my_widget.dart
  state/
    my_widget_state.dart
  view_model/
    my_widget_view_model.dart


2. ビュー

myWidgetStateProvider という変数を利用できることで、ボタンウィジェット、テキストウィジェットでMyWidgetStateの値を利用できる。

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:my_widget_project/state/my_widget_state.dart';
import 'package:my_widget_project/view_model/my_widget_view_model.dart';

// ウィジェット全体
class MyWidget extends StatelessWidget {
  const MyWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return ProviderScope(
      child: Scaffold(
        body: Container(
          padding: const EdgeInsets.all(20),
          child: const Column(
            children: [
              MyTextWidget(),
              MyButtonWidget(),
            ],
          ),
        ),
      ),
    );
  }
}

// テキストウィジェット
// counterを表示
class MyTextWidget extends ConsumerWidget {
  const MyTextWidget({super.key});
  
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final int counter = ref.watch(myWidgetStateProvider).counter;
    return Text('$counter');
  }
}

// ボタンウィジェット
// ボタンを押すとincrementメソッドを呼び出してcounter値を変化させる
class MyButtonWidget extends ConsumerWidget {
  const MyButtonWidget({super.key});
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final Function increment = ref.read(myWidgetStateProvider.notifier).increment;
    return ElevatedButton(
        onPressed: () => increment(), child: const Text("MyWidget"));
  }
}

// MyWidgetStateへのグローバルアクセスを提供するもの
final myWidgetStateProvider =
    StateNotifierProvider<MyWidgetStateNotifier, MyWidgetState>(
  (ref) => MyWidgetStateNotifier(),
);


3. ステート

my_widget_state.dartの中身として、counterという変数を持つクラスを定義。copyWithというメソッドでcounterの新しい値を受け取ってstateを更新する。

class MyWidgetState {
  const MyWidgetState({this.counter = 0});

  final int counter;

  // copyWithメソッドを定義する
  MyWidgetState copyWith(int counter) => MyWidgetState(counter: counter);
}


4. ロジック

my_widget_view_model.dartの中身として、StateNotifierを継承したクラスを定義。incrementというメソッドで、stateを更新するcopyWithへのアクセス手段を提供する。

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:my_widget_project/state/my_widget_state.dart';

// ロジック
class MyWidgetStateNotifier extends StateNotifier<MyWidgetState> {
  MyWidgetStateNotifier()  : super(const MyWidgetState());

  void increment() {
    state = state.copyWith(state.counter + 1);
  }

}


5. main.dart

MyWidgetをインポートして利用する。

import 'package:flutter/material.dart';
import 'package:flutter_routing/screen/my_widget.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      // MyWidgetを使う
      home: const MyWidget(),
    );
  }
}



まとめと今後の課題

Riverpodを使った基礎的なコーディング方法を整理できたので、活用していきたい。