【Flutter】VueエンジニアがスマホアプリもMVVMパターンで開発してみる。

## はじめに
この記事は、MVVMパターンの代表として語られるVue.jsを使ってwebアプリ開発をしているエンジニアが、MVVMパターンを用いてサンプルのflutterアプリを開発してみるもの。

もともとflutterでのアプリ開発でMVVMパターンが採用されることは少なくない。
なぜならAndroidアプリの開発ではMVVMパターンが推奨されており、AndroidエンジニアがFlutterでもスムーズに開発できるようにするためだ。

一方で筆者はAndroidアプリエンジニアではないが、同じくMVVMパターンのフレームワークであるVueのエンジニアである。
そのため、Flutterでのアプリを作成するにあたり、Vue.jsで作り慣れた構成にてアプリを作ってみよう、と思ったのだ。

この記事の実装内容は、以下のgithub repositoryに作成していく。

https://github.com/ninoko1995/mvvm_sample

Flutterアプリ+MVVMパターンの参考記事：
> [【Flutter入門】100画面を超える規模のアプリを開発してみた感想](https://pentagon.tokyo/app/2937/#toc_id_7)
> [Flutter を MVVM で実装する](https://wasabeef.medium.com/flutter-%E3%82%92-mvvm-%E3%81%A7%E5%AE%9F%E8%A3%85%E3%81%99%E3%82%8B-861c5dbcc565?p=861c5dbcc565)

## そもそもどんなパターンがあるの？
### アーキテクチャパターン
そもそもFlutter開発のアーキテクチャパターンの選択肢としては、大別すると以下のようなものがあるらしい。

- MVVMパターン
- Reduxパターン
- mobXパターン

**MVVMパターン**
以下の記事で解説した。

https://qiita.com/ninoko1995/items/1912d1d6cfd0b5785d09

一言で言うと、MVCパターンの、CとVがデータのやりとりではなくdatabindをしているもの、というイメージ。

**Reduxパターン**
Vue.jsエンジニア的には、以下の記事がわかりやすい。

https://speakerdeck.com/shinpeim/vue-dot-jsdeshi-xian-surumvvmpatan-fluxakitekutiyatofalseju-li

https://qiita.com/syossan27/items/7e1b2e07ac68b96bdaa7

MVVMとFluxの違いは、データの流れを一方向に制限されているかどうかという点。
で、ReduxはFluxの亜種的なもの、くらいの理解。


**mobXパターン**
以下の記事を読むといい。

https://itome.team/blog/2019/12/flutter-advent-calendar-day23/

> MobXはReduxと同じくJavascriptのために考えられた設計で、主にReactと共に使われ

ているらしい。

### 状態管理パターン
で、これらのアーキテクチャパターンを実現するための、つまりdatabindを実現するための状態管理の方法として、以下のような選択肢があるらしい。

| 伝達の受信 | 状態の伝達(保存？) |
|:-|:-|
|StatefulWidget  | StatefulWidget |
| InheritedWidget| InheritedModel |
|ChangeNotifier or ValueNotifier|Provider or Riverpod|　
|StateNotifier + freezed|Provider or Riverpod  |
|BLoC|Provider or Riverpod |

https://docs.flutter.dev/development/data-and-backend/state-mgmt/options

https://swiftfe0.hatenablog.com/entry/2020/06/24/113145

https://medium.com/flutter-jp/state-1daa7fd66b94

### 状態管理パターンの選定

今回は、複数のWidgetから同じ変数にアクセスしたいので、StatefulWidgetは除外。

> ある状態変数を２つ以上のWIdgetが変更したり、子Widgetで状態変数が変更されて親Widgetでその状態変数を利用するような場合は、package:provider + ChangeNotifier、あるいはpackage:provider + BLoCなどを使った方が管理しやすいです。

> 出典：[Flutterアプリの主流な状態管理パターンと導入事例まとめ（2020年版）#パターン1-StatefulWidget](https://swiftfe0.hatenablog.com/entry/2020/06/24/113145#%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B31-StatefulWidget)

InheritedWidget使うならProviderを使え、と言う言説が至る所にあるのでInheritedWidgetは除外。

> inheritedWidgetはわかりにくくコード量も無駄に多くなるということで、package:providerが登場しました。現状、package:providerでできるような用途の場合、package:providerを使いましょう。

> 出典：[Flutterアプリの主流な状態管理パターンと導入事例まとめ（2020年版）#パターン2-InheritedWidget--InheritedModel](https://swiftfe0.hatenablog.com/entry/2020/06/24/113145#%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B32-InheritedWidget--InheritedModel)

ChangeNotifierかStateNotifierかでいうと、値はimmutableにしたいので、StateNotifierを採用。

> mutableで簡単に済ませたいし、それで問題を感じない → ChangeNotifier
> 状態クラスの扱いが多少煩わしくなるのを許容してでもimmutableの恩恵を得たい → StateNotifier

> 出典：[Flutterの状態管理手法の選定](https://medium.com/flutter-jp/state-1daa7fd66b94)

ProviderかRiverpodかでいうと、あえてProviderを使う理由もないのでRiverpodを採用。

> 純粋に機能的にProviderがRiverpodより優れている点は思い浮かびません。そのため、個人的には今後Providerをあえて選ぶことはないと思います

> 出典：[Flutterの状態管理手法の選定](https://medium.com/flutter-jp/state-1daa7fd66b94)


と言うことで、今回は
`StateNotifier + freezed +  Riverpod`
と言う構成でアプリを作ってみる！

`StateNotifier`か`ChangeNotifier`かは、両方作ってみて使いやすいのはどちらかを考えてみる。

## 実際に作成

参考にする記事は、以下たち。

**MVVMの参考記事**

https://wasabeef.medium.com/flutter-%E3%82%92-mvvm-%E3%81%A7%E5%AE%9F%E8%A3%85%E3%81%99%E3%82%8B-861c5dbcc565

https://zenn.dev/alesion/articles/ab2df82a3809b7

https://note.com/yasukotelin/n/n3cdd311fb336

**StateNotifier + freezed +  Riverpodの参考記事**

https://zenn.dev/matsumaru/articles/622c2376abb8bf

https://zuma-lab.com/posts/flutter-todo-list-riverpod-use-provider-state-notifier-freezed

ちなみにflutterのversionはこんな感じ。

```
$ flutter --version
Flutter 2.8.1 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 77d935af4d (5 weeks ago) • 2021-12-16 08:37:33 -0800
Engine • revision 890a5fca2e
Tools • Dart 2.15.1
```

以前stableではないchannelを用いてしまったせいでハマったことがあるので、確認を怠らないようにしないと。

### プロジェクトの作成

とりあえず、プロジェクトを作成して初期状態に。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270898/a232a72c-add3-6159-6b22-96fa7a6c151d.png" width=300>

### ファイルの分割

この最初のページを書き換えていく。
とりあえず、ファイルを分割してフォルダを整理。
該当コミット: [686fa79f27d4eab3fec75fe3f71291fceb704260](https://github.com/ninoko1995/mvvm_sample/commit/686fa79f27d4eab3fec75fe3f71291fceb704260)

### Providerデザインパターンの採用

https://zenn.dev/kazutxt/books/flutter_practice_introduction/viewer/advanced_provider

ViewModelを作成
ChangeNotifierを導入することで、stateful widgetをstateless widgetに書き換える。
これをすることで、状態を保持する変数を、別ファイルに記述することができるようになるのが嬉しい。
見た目を記述する部分と値のロジックを記述する部分の分離。

該当commit: [ae222520013ec557a807acadb5014581a1c3002e](https://github.com/ninoko1995/mvvm_sample/commit/ae222520013ec557a807acadb5014581a1c3002e)

### Riverpod + Hooksの導入

https://zenn.dev/kazutxt/books/flutter_practice_introduction/viewer/advanced_riverpod

ViewModelを更新
StateNotifierとHookWidgetを用いて、ChangeNotifierで記述していた部分をより簡略化して書けるようになった。

StateNotifierの導入: [46af4f55be8abc554549ab460494fa8f17537b59](https://github.com/ninoko1995/mvvm_sample/commit/46af4f55be8abc554549ab460494fa8f17537b59)
Hooksの導入: [b84eba716b959dbbba8af7311888809441312913](https://github.com/ninoko1995/mvvm_sample/commit/b84eba716b959dbbba8af7311888809441312913)


上記の記事だと、riverpodのversionが`0.14.0+3`だったので、`^1.0.0`に更新する。

https://zenn.dev/riscait/books/flutter-riverpod-practical-introduction/viewer/migrate-to-v1

更新commit: [e45d4a8e0bca15ca315d515fa6ee97d4c14ff8b3](https://github.com/ninoko1995/mvvm_sample/commit/e45d4a8e0bca15ca315d515fa6ee97d4c14ff8b3)

ちなみに、Riverpodのパッケージには以下のような種類がある。
（出典：[Flutter#2　〜Flutterの状態管理パターン （Riverpod）〜](https://www.ariseanalytics.com/activities/report/20210625/)）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270898/77a23b9a-67c4-785d-f80e-72a81d408b6e.png)

### freezedの導入

freezedは、immutableなクラスを作成するためのpackage。
このパッケージが必要な理由は以下。

**理由1**

> Flutterの中で状態管理をしても、たまに値の変更が検出されず画面が更新されないことがある。
> Flutterの状態管理では、参照先の「インスタンスが変わったか」によって、値の変更を検出する。しかし、インスタンス内で値が変わっても、そのインスタンスが変更されないため、状態管理としては、「値は変わっていない」と判断される。
> 出典： [【Flutter/Dart】状態管理に必要なfreezedパッケージを徹底解説 クラスの作成・生成方法からチップスまで【2021年12月版】](https://flutter.salon/plugin/freezed/)

一言で言うと、「インスタンスを変更することで再レンダリングを正しく行わせるため」。

**理由2**

> ChangeNotifierでは、状態の変更を通知するために、状態を変更するたびに notifylisteners関数を呼ぶ必要があります。この煩わしさを解消してくれたのが、StateNotifierになります。
> StateNotifierは、一つの状態（state）しか持つことができません。そのため、複数の状態を管理したい場合は、オブジェクトを作成して管理することになります。
> (中略)
> StateNotifierで利用するState用のクラスをimmutableにすると、StateNotifier側の記述が冗長になってしまいます。これを解決するのがfreezedパッケージになります。
> 出典: [Flutter#2　〜Flutterの状態管理パターン （Riverpod）〜](https://www.ariseanalytics.com/activities/report/20210625/)

一言で言うと、「StateNotifierの記述を簡潔にするため」。

他にも、色々freezedの説明をしてくれている記事は以下。

https://flutter.salon/plugin/freezed/

https://zuma-lab.com/posts/flutter-todo-list-riverpod-use-provider-state-notifier-freezed

さて、これらの記事を参考に、アプリにfreezedを導入していく。
現状ではクラスを変数として保持していないので、modelを追加してそのmodelクラスを保持するようにコードを修正する。（このmodelはMVVMパターンでいうところのModelとは別）

更新commit: [b4aff7792570bfa8b41d4144afe3ab8970941b00](https://github.com/ninoko1995/mvvm_sample/commit/b4aff7792570bfa8b41d4144afe3ab8970941b00)

その後、保持されたmodelをimmutableとするよう、state（MVVMでいうところのModel）ファイルを作成し、stateファイルにfreezedを導入する。

更新commit: [fec7777642ea3caf55be45e5c5c149a404a7c2a3](https://github.com/ninoko1995/mvvm_sample/commit/fec7777642ea3caf55be45e5c5c149a404a7c2a3)

## まとめ

と言うことで、アプリの挙動自体は変わっていないが、デフォルトアプリのMVVMパターンへの書き換えが完了した。
最終的なフォルダ構造は以下。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270898/d03a9dcc-9a2d-b650-e87f-6d0535f2e003.png" width=500>

`/state` -> MVVMパターンでいうところのModel。VueでいうところのStoreファイル。
`/ui/home_view.dart` -> MVVMパターンでいうところのView。VueでいうところのViewファイルのhtml部分。
`/ui/home_view_model.dart` -> MVVMパターンでいうところのViewModel。VueでいうところのViewファイルのscript部分。
`/model` -> ただのデータモデル。MVVMパターンのModelとは別。Vueでいうところのmodel。
`/provider` -> ViewModelの変更をViewに検知させるためのもの。Vueでデータバインドは勝手にやってくれてる。

これで、最小のMVVMパターンでのアプリが完成した。
MVVMパターンを採用してアプリを作成するとはどういうことか、が理解できた気がする。
まだ通信を伴ってはいないので今後はこれにrepositoryを導入していくことも必要だが、一旦MVVMパターンを理解した、ということで一区切り。