🏠

【Flutter】Riverpodでオニオンアーキテクチャ+MVVMのファイル構成

に公開

Flutterで中〜大規模アプリを作るとき、UIロジックとドメインロジックの分離、依存の方向性、テスタビリティをどう担保するかが重要だと思います。
この記事では オニオンアーキテクチャをベースに、MVVMをRiverpodで実装する際のファイル構成の提案と最小サンプルを提示します。

なお、より良いファイル構成がないかこの記事を書き終わった後も尚考察中ですので、ご意見ご感想ございましたらコメント頂けますと幸いです。

オニオンアーキテクチャとは

Jeffrey Palermo氏により考案されたアーキテクチャパターンです。
内側ほどビジネスに近く、外側ほど技術的詳細に近い、同心円状の構造です。
依存の方向は外から内に向かい、逆方向への依存は禁止です。

クリーンアーキテクチャもよく聞くと思います。違いに関して気になる方は、参考記事に貼ってありますリンク先で確認してみてください。

MVVMとは

Model、View、ViewModelの3つからなり、各々以下役割を担います

  • Model
    • ドメインのデータや永続化層との通信する
  • ViewModel
    • UIが必要とする状態(AsyncValue<T> など)と操作(メソッド)を公開する
  • View
    • 画面実装。ConsumerWidget等でViewModelの状態を購読する。ボタンタップイベント等は ViewModelに委譲する

サンプルコード

https://github.com/sakku-14/learn_app/tree/main/lib/riverpod_learn
こちらにソースコードを置いてますので、必要に応じてご確認いただければと思います。

RiverpodでMVVMにするときのファイル構成

サンプルコードは以下構成となっています。
ポイントはオニオンアーキテクチャの角層でフォルダを切り、その中にMVVMに合わせてフォルダが切られてます。

├── application         # アプリケーション層
│   ├── dtos
│   │   ├── item_dto.dart
│   │   ├── item_dto.freezed.dart
│   │   └── item_list_dto.dart
│   └── usecases
│       ├── fetch_item_list_usecase.dart
│       └── update_item_usecase.dart
├── domain              # ドメイン層
│   ├── models              # 【Model】
│   │   ├── item.dart
│   │   ├── item.freezed.dart
│   │   ├── item_list.dart
│   │   └── item_list.freezed.dart
│   └── repositories
│       └── item_list_repository.dart
├── infrastructure      # インフラ層
│   └── repositories
│       └── item_list_repository.dart
└── presentation        # プレゼンテーション層
    ├── providers
    │   ├── item_detail_page_view_model_provider.dart
    │   └── item_list_page_view_model_provider.dart
    ├── view                # 【View】
    │   ├── pages
    │   │   ├── item_detail_page.dart
    │   │   └── item_list_page.dart
    │   └── widgets
    │       └── todo_list_tile.dart
    └── view_model          # 【ViewModel】
        ├── item_detail_page_view_model.dart
        ├── item_list_page_view_model.dart
        └── notifiers
            ├── item_list_notifier.dart
            └── states
                ├── item_list_state.dart
                ├── item_list_state.freezed.dart
                ├── item_state.dart
                └── item_state.freezed.dart

まとめ

一つの提案として、執筆させていただきました。
以前、C#でMVVMを採用したプロジェクトを担当したことがあり、その後Flutterで開発する時にRiverpodでMVVMを採用するという記事があまり見かけなかったのでいつか執筆したいと思い、今回執筆するに至りました。今では他にも執筆されてる方もいらっしゃいますので他の記事も参考に自分なりのファイル構成を研究していくと良いかと思います。

今回は、ファイル構成に焦点を当てており、依存関係をブロックするような仕組み(Lint)に関しては言及していません。そちらに関しても技術的には可能ですので、気になる方は挑戦してみてください。

参考記事

Discussion