1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Flutter】RiverPodでのMVVMからSSOTへの移行について【アーキテクチャ】

Last updated at Posted at 2025-12-06

はじめに

最近ようやくSSOTという概念を知った初心者ではありますが、現時点でわかったことをまとめてみました。
MVVM前提でのSSOTの話になります。

SSOTとは

SSOT(Single Source of Truth)とは、「唯一の信頼できる情報源」のことで、
どういうことかというと、
MVVMだとViewに紐づくViewModelの中に表示用する内容のDataがあると思いますが、このDataをViewModelから分離させて、一元管理しようというものです。

例えば、MVVMにおいて、「投稿」という機能があったとすると、
・投稿一覧画面(PostListView)
・投稿詳細画面(PostDetailView)
があると思います。
SSOTでないPJは、PostListViewとPostDetailViewでは、それぞれに別の一覧のPostのDataと、詳細のPostのDataを持っていると思います。
SSOTでは、このPostのDataはStoreとして、Post一覧とPost詳細のDataを同じものとして一元管理します。

具体例を挙げると、
SSOTでないMVVMのPJで特にそれ用の処理を入れていない場合、投稿詳細画面でいいねを押した後、popで画面を戻って投稿一覧画面に戻った時、つけたはずのいいねが消えていると思います。
これがSSOTだとPostのDataは一元管理されているので、詳細画面でいいねをつけた時に一覧画面のUIも更新する処理を入れるだけで、詳細画面から一覧画面に戻ったとき、いいねがついている状態にできるということになります。

具体例のコード

SSOTで一元管理するDataは、Storeクラスとして管理するのが本来のSSOTのよう?なんですが、RiverPodの場合はStoreクラスとして一元管理するより、Provider内の変数としてStoreを入れて、直接ViewからwatchするというのがRiverPod的には良いようです。
以下はStateNotifierProviderでの例です。

@freezed
class PostState with _$PostState {
  const factory PostState({
    @Default(false) bool isInit,
    @Default([]) List<int> listPostId, // 一覧UI更新用
    int? detailPost, // 詳細UI更新用
  }) = _PostState;
}

final postProvider = StateNotifierProvider<PostProvider, PostState>(
  (ref) => PostProvider(ref: ref),
);

class PostProvider extends StateNotifier<PostState> {
  PostProvider({required Ref ref})
      : _ref = ref,
        super(const PostState()) {
    init();
  }

  final Ref _ref;
  Map<int, Post> _postStore = {};

  void init() {
    state = state.copyWith(isInit: true);
  }

  /// 投稿一覧取得
  Future<void> fetchPosts() async {
    try {
      final postList = await PostRepository.fetchPostList();
      // --- SSOT (_postStore) 更新 ---
      for (final p in postList) {
        _postStore[p.id] = p;
      }

      // --- UI 用 state 更新 ---
      state = state.copyWith(
        listPostId: postList.map((e) => e.id).toList(),
      );
    } catch (e) {
      LogUtil.d('投稿一覧取得失敗: ${e.toString()}');
    }
  }

  /// 投稿詳細取得
  Future<void> fetchPost(String postId) async {
    try {
      final post = await PostRepository.fetchPost(postId);
      // --- SSOT (postStore) 更新 ---
      _postStore[post.id] = post;

      // --- UI 用 state 更新 ---
      state = state.copyWith(detailPost: post.id);
    } catch (e) {
      LogUtil.d('投稿詳細取得失敗: ${e.toString()}');
    }
  }

  //一覧画面更新
  void reloadPostList() {
    state = state.copyWith(
      listPostId: [...state.listPostId], // 新しい List を作ってUI更新
    );
  }

}

ListのViewModel側の呼び出しコード

final postState = ref.watch(postProvider);
final postStore = ref.read(postProvider.notifier).postStore;

final posts = postState.listPostId.map((id) => postStore[id]).toList();

DetailのViewModel側の呼び出しコード

final postState = ref.watch(postProvider);
final postStore = ref.read(postProvider.notifier).postStore;

final postId = postState.detailPost;
final post = (postId != null) ? postStore[postId] : null;

SSOTのデメリット

投稿一覧画面にページネーションがあった場合、管理が少し難しくなります。
_postStoreにデータを溜めていく(表示されているページ外の表示済みPostのDataも持ち続ける)のは変わらないと思うんですが、
この場合、画面側ではなく上記のPostStateとPostProviderにページの情報持たせるのがいいのかなーと思います。

あとがき

もともとRiverPodは設計思想的にMVVMよりもSSOTが向いているようです。
SSOT的には、ページで持たせたい状態はViewModelにではなくStatefulWidgetで持たせましょうね、ということらしいです。
すでにMVVMに慣れてしまっているとSSOTをこれから取り入れていくのはコスト高いかもしれませんが、データの一元管理というのはすっきりとした綺麗なアーキテクチャに近付くものだと思うので、ぜひ取り入れてみてください。

SSOTでないMVVMのPJで特にそれ用の処理を入れていない場合、投稿詳細画面でいいねを押した後、popで画面を戻って投稿一覧画面に戻った時、つけたはずのいいねが消えていると思います。
これがSSOTだとPostのデータは一元管理されているので、詳細画面でいいねをつけると一覧画面のDataも更新されるので、詳細画面から一覧画面に戻ったとき、いいねがついている状態にできるということになります。

上記でこのように書いたので、MVVMでこのいいねを更新する方法を挙げると、
・pop時にthenで値を前の画面に渡して更新
・UI部品で画面が表示されたことを検知してAPIを叩き直す
・DetailViewModelからListViewModelのstateを変更する
このような方法があるかなと思います。
この中だと、DetailViewModelからListViewModelのstateを変更するのが一番楽かなと思います。
手間としてはSSOTの詳細でいいねつけたときのList画面用の更新処理入れるのと変わらないんですが、SSOTの方だと参照しているデータが同じなので一元管理されていて綺麗かなとは思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?