0
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】MVVMとは?Flutter(DartPad)で動く最小デモ + シーケンス図で理解する

Posted at

MVVMとは?Flutter(DartPad)で動く最小デモとシーケンス図で理解する

この記事では MVVM(Model–View–ViewModel) について、
概念説明 → 動くデモコード → 図解(シーケンス図・構造図) の順で解説します。

Flutter × DartPad(ブラウザSandbox)を使い、
環境構築なし・1ファイル完結で理解できる構成にしています。


MVVMとは?

MVVMは、UIアプリケーションを次の3つの役割に分離する設計パターンです。

  • Model
    アプリが扱うデータ・状態(例:カウント値、ユーザー情報)
  • View
    画面(FlutterでいうWidget)。表示とユーザー操作のみを担当
  • ViewModel
    ViewとModelの橋渡し役。状態管理・ロジックを担当

一言でいうと

UI(View)からロジックを切り離し、画面を薄く保つための設計


なぜMVVMを使うのか?

メリット

  • UIとロジックの責務が分離され、コードが読みやすい
  • ViewModelはUIに依存しないためテストしやすい
  • UI変更(iOS / Android / Web)に強い

デメリット

  • 小規模アプリでは分割が過剰に感じることがある
  • 責務を意識しないと「なんちゃってMVVM」になりがち

今回のデモでやること

  • カウント表示
  • +1 ボタンでカウント増加
  • reset ボタンで初期化

※ DartPad(Flutter)は 実質1ファイルのみ扱えるため、
今回は 「1ファイルMVVM(擬似MVVM)」 として実装します。


デモコード(DartPad対応・1ファイルMVVM)

import 'package:flutter/material.dart';

void main() => runApp(const App());

/// =======================
/// Model(状態データ)
/// =======================
@immutable
class CounterState {
  final int count;
  const CounterState({required this.count});

  CounterState copyWith({int? count}) {
    return CounterState(count: count ?? this.count);
  }
}

/// =======================
/// ViewModel(状態管理・ロジック)
/// =======================
class CounterViewModel extends ChangeNotifier {
  CounterState _state = const CounterState(count: 0);

  CounterState get state => _state;

  void increment() {
    _state = _state.copyWith(count: _state.count + 1);
    notifyListeners();
  }

  void reset() {
    _state = const CounterState(count: 0);
    notifyListeners();
  }
}

/// =======================
/// View(UI)
/// =======================
class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: CounterPage(),
    );
  }
}

class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  final vm = CounterViewModel();

  @override
  void dispose() {
    vm.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('MVVM Demo (DartPad)')),
      body: Center(
        child: AnimatedBuilder(
          animation: vm,
          builder: (context, _) {
            return Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Text(
                  'count: ${vm.state.count}',
                  style: const TextStyle(fontSize: 32),
                ),
                const SizedBox(height: 16),
                Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    ElevatedButton(
                      onPressed: vm.increment,
                      child: const Text('+1'),
                    ),
                    const SizedBox(width: 12),
                    OutlinedButton(
                      onPressed: vm.reset,
                      child: const Text('reset'),
                    ),
                  ],
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}

MVVMのデータフローをシーケンス図で理解する

MVVMでは、データと処理の流れが一方向になることが重要です。
ここでは「ボタンを押してカウントを1増やす」ケースを例に、
MVVM内部で何が起きているのかをシーケンス図で見ていきます。


基本的な流れ

  1. ユーザーが画面(View)を操作する
  2. View が ViewModel のメソッドを呼び出す
  3. ViewModel が Model(状態)を更新する
  4. ViewModel が View に変更を通知する
  5. View が最新の状態を使って再描画される

ポイントは、View が直接 Model を変更しないことです。


シーケンス図(Mermaid)

図から読み取れること

上記のシーケンス図から、MVVMの設計上の重要なポイントが読み取れます。

  • View はユーザー操作の受付に専念している
    ボタンタップなどのイベントを受け取り、
    ロジックは持たず ViewModel に処理を委譲している。

  • ViewModel が状態変更の唯一の窓口になっている
    状態(Model)の更新は必ず ViewModel を経由するため、
    ビジネスロジックの所在が明確になる。

  • Model は純粋なデータとして扱われている
    Model 自身は振る舞いを持たず、
    View や ViewModel から独立した存在になっている。

  • 状態変更は通知を通じて View に伝播する
    ViewModel が状態更新後に通知を出し、
    View はそれをトリガーに再描画される。

  • データフローが一方向である
    View → ViewModel → Model → View という流れが守られ、
    双方向依存や直接参照が発生していない。


これらのポイントにより、

  • UI とロジックが疎結合になる
  • 影響範囲が限定され、変更に強くなる
  • ViewModel を単体でテストしやすくなる

といった MVVM のメリットが得られます。

0
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
0
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?