flutter_riverpodを使ってモデルとビューに分離した状態管理を学ぶ

 

カウンターアプリを作りながらflutter_riverpodについてカウンターアプリを作成しながら、ConsumerWidget, Providerについて紹介していきます。

その中でモデル(model)とビュー(view)を分けることにより、コードリーディングを上げるための考え方についても触れていきます。

最終的な成果物

環境

Flutter 3.10.2

Dart 3.0.2

flutter_riverpodとは

Flutter専用の状態管理パッケージです。

状態管理ってなんか難しい言葉ですが、
つまりflutter_riverpodを使ってどこのWidgetからでもアクセスできるグローバルな変数を
作っちゃおう!ということです

flutter_riverpodを使った状態管理をすることで他のファイルからも、それらにアクセスすることができます。

正直、flutter_riverpodがなくてもアプリを作ることはできます。(ただ、神クラスを作りかねない)

flutter_riverpodがあることで開発のコードリーディングを向上させたりすることができます。

アプリ自体が大きくなることで状態とビュー側を分けることにより管理がしやすくなるということです。

実装方針

  1. flutter_riverpodをインストールする
  2. ConsumerWidget, ProviderScopeを実装
  3. Providerを使ってロジックを分離する
  4. カウントできるボタンの実装

flutter_riverpodをインストールする

以下のコマンドをプロジェクトのルートディレクトリにて実行しましょう。

flutter pub add flutter_riverpod

ConsumerWidget, ProviderScopeの実装

ProviderScopeをMyAppにラップし、状態管理した値を利用するウィジェットにてConsumerWidgetをextendsしています。

また、ConsumerWidgetのbuildメソッドをオーバーライドする際にWidgetRefという引数を追加します。

main.dart

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

void main() {
  runApp(
    ProviderScope(
      child: 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,
      ),
      home: Home(),
    );
  }
}

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      body: SafeArea(
        child: Text("hello"),
      ),
    );
  }
}

ChangeNotifierProviderを使ってロジックの分離を行う

カウンター部分を実装していくに当たってロジックの分離をおこなっていきます。

ChangeNotifierProviderとは別の場所に保管している状態管理変数を
Widget側で見れるようにするパイプみたいな役割を持っています。

今回は以下の2つで分けて実装します。。

ファイル名目的
modelロジック系の処理を集める
view画面系の処理を集める

また、それらはhomeというディレクトリの中に作成することとします。

// ディレクトリのイメージ
lib --  main.dart
      |-  home --  model.dart
                      |-  view.dart

わざわざhomeディレクトリの中にview, modelを作成する理由はコードリーディングを向上するため
ですが、詳細は後述します

main.dart

import 'package:tmp_blog/home/view.dart';
...
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,
      ),
      home: Home(),
    );
  }
}

home/view.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:tmp_blog/home/model.dart';

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final CounterModel counterModel = ref.watch(homeProvider);
    return Scaffold(
      body: SafeArea(
        child: Text(counterModel.count.toString()),
      ),
    );
  }
}

home/model.dart

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

final homeProvider = ChangeNotifierProvider((ref) => HomeModel());

class CounterModel extends ChangeNotifier {
  int count = 10;
}

Widget内で変数を管理することも可能ですが、大きなアプリになってくると、一つのWidgetで書いていくには誰も読めないコードを作ってしまうことになりかねません。

そのためコードリーディングを向上させる目的でロジックの分離を行います。

単一責任の原則といって1つのクラスには1種類の処理のみを任せるべきというコードリーディングの考えに則ったものです。

今回作るカウンターアプリ自体はHome(いわば初期の画面)のみで実装しますが、
例えば以下の例も追加で実装するとなると単一責任はどうなるでしょうか?

作成したい機能ディレクトリ名
過去のカウントの履歴を見るCounterHistory
アプリのテーマスタイルを変更するTheme

ディレクトリを作らずに実装していた場合はmodel.dart, view.dartの中に
Home, CounterHistory, Themeを記述することになり、
何がどこに書いてあるかが、他の人には分からなくなってしまいます。

それを解析するのに無駄なコードリーディングの時間が費やされてしまいます。

model, viewに実装されているフォーマットはスニペットにしておくと、今後楽ですね!

カウントをプラス・マイナスできるボタンを追加

最後にカウンターアプリっぽく体裁を整えていきます。

ElevateButtonウィジェットを使ってボタンを配置しました。

view.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:tmp_blog/home/model.dart';

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final CounterModel counterModel = ref.watch(homeProvider);
    return Scaffold(
      body: SafeArea(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                counterModel.count.toString(),
                style: TextStyle(
                  fontSize: 40,
                ),
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  ElevatedButton(
                    onPressed: () {
                      counterModel.plus();
                    },
                    child: Text("+"),
                  ),
                  ElevatedButton(
                    onPressed: () {
                      counterModel.minus();
                    },
                    child: Text("-"),
                  ),
                ],
              )
            ],
          ),
        ),
      ),
    );
  }
}

countをプラス・マイナスするメソッドを作成し、
notifyListeners()にてcountの変更結果をProviderを通して伝えるようにしました。

notifyListeners()がないと変更を検知できず、ボタンを押下しても何も反応しません。

model.dart

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

final homeProvider = ChangeNotifierProvider((ref) => CounterModel());

class CounterModel extends ChangeNotifier {
  int count = 10;
  void plus() {
    count = count + 1;
    notifyListeners();
  }

  void minus() {
    count = count - 1;
    notifyListeners();
  }
}

まとめ

ここまで読んでいただきありがとうございます

今回はflutter_riverpodを使って状態管理を実装しました。

その中でコードリーディングについても触れました。

そのまま実装に使うことは難しいものの、今後のコード記述の考え方に一役買えたら幸いです。

コメント

タイトルとURLをコピーしました