Flutter

【Flutter】MVVMをRiverpodで実装する!サンプルを用いて解説

こんにちは、テルプロです!

「MVVMをRiverpodで実装する方法は?」とお悩みではないでしょうか。

テルプロ

本記事ではそんな悩みを解決していきます!

本記事を読むことで
  1. MVVMの基本設計をサンプルで理解できる
  2. Riverpod + freezedの使い方がわかるようになる
  3. コードを公開しているので、自分の環境で確かめることができる

MVVMをRiverpodで実装する【サンプル】

事前準備

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

今回使用するパッケージは以下の通りです。

dependencies:
  build_runner: ^2.1.8
  flutter:
    sdk: flutter
  flutter_riverpod: ^1.0.3
  freezed: ^1.1.1
  freezed_annotation: ^1.1.0

dev_dependencies:
  flutter_lints: ^1.0.0
  flutter_test:
    sdk: flutter

プロジェクト構成

リポジトリ構成

完成イメージ

今回実装するアプリは、数字が2つあり、数字をクリックすると値が(+1)されていき、ボタンをクリックすると値が(0)になるというシンプルなアプリです。

それでは、MVVMでこちらのアプリを作っていきましょう!

ソースコード

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_mvvm/view/home_page.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter MVVM',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

model / home_page_state.dart

まずは、モデルとなるクラスを作成していきましょう。

今回は「mainCount」と「subCount」という2つの値を用意します。

クラスの作成にはfreezedを使用しています。freezedを使用することで、不変クラスを作成することが出来ます。

import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

// 生成されるファイル名を指定する( `生成元ファイル名.freezed.dart` )
part 'home_page_state.freezed.dart';

@freezed
class HomePageState with _$HomePageState {
  const factory HomePageState({
    @Default(0) int mainCount,
    @Default(0) int subCount,
  }) = _HomePageState;
}

上記の内容が書き終わったら、こちらのコマンドを回してください。

flutter pub run build_runner watch --delete-conflicting-outputs

これにより、ファイルが自動生成されます。–delete-conflicting-outputsを加えることで、クラスに変更が加えられる度に再生成されます。

modelフォルダが上記のようになっていれば問題ありません。これで、データを保持するクラスを用意することが出来ました。

view_model / home_page_notifier.dart

次に「View」と「Model」を紐つける役割を担う「ViewModel」を作成していきます。

状態管理にはRiverpodを使用しています。Riverpodを使用することで値をグローバル定数として宣言できるため、どのWidgetからもデータを取得できます。

操作内容をStateNotifierにまとめ、StateNotifierProviderで状態を管理します。

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_mvvm/model/home_page_state.dart';

class HomePageNotifier extends StateNotifier<HomePageState> {
  // 初期値の指定
  HomePageNotifier() : super(const HomePageState());

  // メインカウントを+1する
  void increaseMainCount() async {
    state = state.copyWith(mainCount: state.mainCount + 1);
  }

  // サブカウントを+1する
  void increaseSubCount() async {
    state = state.copyWith(subCount: state.subCount + 1);
  }

  // すべてのカウントを0に戻す
  void resetAllCount() async {
    state = state.copyWith(
      mainCount: 0,
      subCount: 0,
    );
  }
}

// HomePageNotifierの状態を管理する
final homePageProvider =
    StateNotifierProvider.autoDispose<HomePageNotifier, HomePageState>(
  (ref) => HomePageNotifier(),
);

view / home_page.dart

最後に、サンプルアプリのUI(見た目)を作っていきます。

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_mvvm/model/home_page_state.dart';
import 'package:flutter_mvvm/view/home_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mvvm/view_model/home_page_notifier.dart';

class HomePage extends ConsumerWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // state(状態)
    final _homePageState = ref.watch(homePageProvider);
    // provider(状態の操作)
    final _homePageNotifier = ref.watch(homePageProvider.notifier);
    return Scaffold(
      appBar: AppBar(title: const Text('Flutter MVVM')),
      floatingActionButton: FloatingActionButton(
        onPressed: _homePageNotifier.resetAllCount,
        child: const Icon(Icons.exposure_zero),
      ),
      body: ListView(
        children: [
          ListTile(
            title: Text('Main Count ${_homePageState.mainCount}'),
            onTap: _homePageNotifier.increaseMainCount,
          ),
          ListTile(
            title: Text('Sub Count ${_homePageState.subCount}'),
            onTap: _homePageNotifier.increaseSubCount,
          ),
        ],
      ),
    );
  }
}

大変お疲れ様でした!以上でアプリは完成です!

今回ご紹介したアプリ全体のソースコードはこちらです。

よろしければ、ご参考にどうぞ。

GitHub:https://github.com/terupro/flutter_mvvm

まとめ

今回は「MVVMをRiverpodで実装する方法」を解説しました。

今回紹介したMVVMは、Flutterを用いたアプリ開発で主流となっている設計方法です。良ければ上記のコードを参考に、色々と試してみてください。

▼以下では、私の実体験に基づいて「Flutterの効率的な勉強法」の具体的な手順を詳しく解説しています。よろしければ、ご参考にどうぞ!

最後までご覧いただきありがとうございました。ではまた!

参考文献

Riverpod 2.0 – Complete Guide (Flutter Tutorial)

ABOUT ME
テルプロ
東京都在住のアプリエンジニア。大学では、ソフトウェア開発の研究に取り組む。長期のエンジニアインターンシップを経て、実務スキルを磨き、現在はフリーランスエンジニアとしても活動中。メインはモバイルアプリ開発。IT関連の記事監修も行い、技術の共有と普及に励んでいます。 監修実績(レバテックフリーランス
Flutter関連の書籍を出版しました!