🐕

RiverpodでViewModelを作ってみた

2024/11/19に公開

概要

RiverpodでViewModelを作成する際に、情報が古いものもあり、ある程度理解するまでに
時間がかかったので記事にまとめました。
網羅的に解説するものではなく、とりあえずRiverpodを使ってMVVMの書き方の基本を知りたい方向けです。
自分と同じようにFlutter始めたばかりの方の参考になれば幸いです。
サンプルのクラス名等を変えれば、使えるようになってます。

サンプルコード

State

ViewModelで持ちたい状態を定義し、freezedでprofile_state.freezed.dartを自動生成します。
Stateのプロパティはご自由に差し替えてください。

  1. freezed_annotation.dartをimport
  2. part 'xxxx.freezed.dart';を記述。(xxxはstateのファイル名)
  3. Stateのclassに@freezedをつける
  4. flutter pub run build_runner build --delete-conflicting-outputs コマンドでxxxx.freezed.dartを生成。
import 'package:freezed_annotation/freezed_annotation.dart';

part 'profile_state.freezed.dart';


class ProfileState with _$ProfileState {
  const factory ProfileState({
    ('') String name,
    (0) int age,
    (false) bool isInit,
    String? description,
  }) = _ProfileState;
}

ViewModel

状態の変更をしたいので、classでProviderを生成します。
生成されるProviderはViewModel名+Providerとなります。(ex. profileViewModelProvider)
buildをoverrideして先ほど作成したStateを返すようにします。

  1. riverpod_annotation.dartをimport
  2. part 'xxx.g.dart';を記述(xxxはViewModelのファイル名)
  3. classに@riverpodをつける
  4. flutter pub run build_runner build --delete-conflicting-outputs コマンドでxxxx.g.dartを生成。
  5. 必要に応じてViewModelにメソッドを定義。
import 'package:flutter_basic/mvvm/profile/profile_state.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'profile_view_model.g.dart';


final class ProfileViewModel extends _$ProfileViewModel {
  
  ProfileState build() {
    return const ProfileState();
  }

  void isInitToggle() {
    state = state.copyWith(
        isInit: !state.isInit
    );
  }
}

View

ViewModelを使用する箇所をConsumerで囲う。
ViewModelの状態は、ref.watch(provider名).でアクセスできる。(サンプルでは、不要な描画を避けるためにselectを使用)
ViewModelのメソッドは、ref.watch(provider名.notifier).でアクセスできる。
( onPressedの中ではwatchでなくreadを使う)

import 'package:flutter/material.dart';
import 'package:flutter_basic/mvvm/profile/profile_view_model.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

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

  
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
          child: Consumer(
              builder: (context, ref, _) {
                final profileName = ref.watch(profileViewModelProvider
                    .select((value) => value.name));
                final isInit = ref.watch(profileViewModelProvider
                    .select((value) => value.isInit));
                return Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Text('名前: $profileName'),
                    Text('isInit: $isInit'),
                    const SizedBox(height: 30,),
                    ElevatedButton(
                      onPressed: () {
                        ref.read(profileViewModelProvider.notifier).isInitToggle();
                      },
                      child: const Text('Change'),)
                  ],);
              }
          ),)
    );
  }
}

最後に

最後までお読みいただきありがとうございます。
コードのサンプルと簡単な説明のみになってますが、サンプルコードを真似しながら
公式ドキュメント等で補完し、学習にお役立ていただければ幸いです。

参考記事

https://riverpod.dev/ja/docs/introduction/why_riverpod
https://pub.dev/packages/freezed
https://zenn.dev/riscait/books/flutter-riverpod-practical-introduction

Discussion