こんにちは。新人プログラマーの岩本です。
今回はFlutterでMVVMを実現するための方法を紹介します。
MVVMは初学者でも扱いやすいアーキテクチャなので、ぜひ最後までご覧ください。
MVVMとは
MVVMとはGUIアーキテクチャの1種です。
GUIアーキテクチャとは、UIとロジックを分離させるもので、MVCやMVPなどがあります。
MVVMは3つの構成要素によって成り立っています。
![](https://rookie-programmer.jp/wp-content/uploads/2024/04/8c7b4f6d3120dfad8218a2e6e9e8a44a-1024x576.jpeg)
名前 | 役割 |
---|---|
View | UI担当 |
ViewModel | ViewとModelの中継役。 Modelの値の変更をViewに通知する役割を持つ。 |
Model | データモデルやAPI通信など。 ViewとViewModel以外の全てを担当する。 |
それぞれの頭文字を取ることで、M(Model)V(View)VM(ViewModel)となります。
ではここからFlutterでMVVMを実現するための方法を紹介します。
またここで紹介する方法はあくまで一例で、絶対のものではありません。
ChangeNotifier
ChangeNotifierとは、Flutter SDKに含まれているクラスです。
notifyListeners()
を呼ぶことで、値の変更をViewに通知することができます。
またView側はChangeNotifierProvider
でViewModelをcreateできます。
言葉で説明しても伝わらないと思うので、実際の実装例を紹介します。
実装例
事前準備
まずは必要なパッケージをインストールします。
今回はProviderのみでOKです。
flutter pub add provider
MVVMを実装
次にview_model.dartを作成し、以下のコードを入力します。
class ViewModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count += 1;
notifyListeners();
}
}
ViewModelはChangeNotifierを継承し、値の変更時にnotifyListeners()を呼び出しています。
次にmain.dart内のMyHomePageクラスを以下のように書き換えます。
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => ViewModel(),
builder: (context, _) {
final viewModel = context.watch<ViewModel>();
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('ChangeNotifier'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'${viewModel.count}',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: viewModel.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
},
);
}
}
ChangeNotifierProviderでViewModelをcreateし、表示する値はViewModelのものを参照するようにしています。
StateNotifier
StateNotifierとは、Providerパッケージの作者の方が作った状態管理のためのパッケージです。
ChangeNotifierと違い、notifyListeners()
を呼ばなくても変更を検知できます。
実装例
事前準備
まず必要なパッケージをインストールします。
今回はRiverpodを使用します。
flutter pub add flutter_riverpod
MVVMの実装
次にhome_page_notifier.dartを作成し、以下のコードを入力してください。
// 状態を操作するclass
class HomePageNotifier extends StateNotifier<int> {
HomePageNotifier(): super(0); // 0で初期化する
void increment() {
state += 1;
}
}
// UIから状態にアクセスできるようにするProvier
// UIはこのProviderを通じて状態にアクセスする
final homePageProvider = StateNotifierProvider((ref) => HomePageNotifier());
StateNotifierを使用するには、以下の2つの要素が必要になります。
- 状態を操作するclass(StateNotifierを継承)
- UIから状態にアクセスできるようにするProvider(StateNotifierProvider)
次にmain.dart内のMyHomePageクラスを以下のように書き換えます。
class MyHomePage extends ConsumerWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final homePageState = ref.watch(homePageProvider);
final homePageNotifier = ref.read(homePageProvider.notifier);
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('StateNotifier'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'$homePageState',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: homePageNotifier.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
homePageProviderをwatchすることで、値の変更を検知してUIが再レンダリングされるようになります。
Notifier
Notifierとは、変更される可能性のある状態を管理するためのProviderです。
使い方はStateNotifierとほぼ同じです。
しかしNotifierの場合は、Riverpod Generatorでコードを自動生成することができます。
実装例
まずは必要なパッケージをインストールします。
flutter pub add flutter_riverpod riverpod_annotation
flutter pub add --dev riverpod_generator build_runner
インストールしたパッケージは以下の通りです。
パッケージ名 | 役割 |
---|---|
flutter_riverpod | FlutterでRiverpodを使用するためのパッケージ |
riverpod_annotation | コード生成のためのアノテーションを提供 |
riverpod_generator | Riverpodのコード生成を行うためのパッケージ |
build_runner | ソースコード生成ツール |
MVVMの実装
次にhome_page_notifier.dartを作成し、以下のコードを入力してください。
part 'home_page_notifier.g.dart';
@riverpod
class HomePageNotifier extends _$HomePageNotifier {
@override
int build() => 0;
void increment() {
state += 1;
}
}
次に以下のコマンドを入力して、コードを自動生成してください。
flutter packages pub run build_runner build
するとhome_page_notifier.g.dartが生成され、homePageNotifierProvider
が追加されていると思います。
次にmain.dart内のMyHomePageクラスを以下のように書き換えます。
class MyHomePage extends ConsumerWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final homePageState = ref.watch(homePageNotifierProvider);
final homePageNotifier = ref.read(homePageNotifierProvider.notifier);
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('Notifier'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'$homePageState',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: homePageNotifier.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
基本的な書き方はStateNotifierと同じで、homePageNotifierProviderをwatchすることで、値の変更を検知することができます。
まとめ
今回はFlutterでのMVVMの実装例を紹介しました。
個人的には、特別な理由がなければNotifierを使うのが良いと思いました。
MVVMは色々な場面で使えるので、ぜひご自身の手で色々と試してみてください。
ここまでのご閲覧ありがとうございました!
参考にした記事
![](https://res.cloudinary.com/zenn/image/upload/s--3biR1f_K--/g_center%2Ch_280%2Cl_fetch:aHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL3plbm4tdXNlci11cGxvYWQvYm9va19jb3Zlci82MGM2NGExODZjLnBuZw==%2Cw_200/v1627283836/default/og-base-book_yz4z02.jpg)
![](https://res.cloudinary.com/zenn/image/upload/s--OlKEQuRK--/c_fit%2Cg_north_west%2Cl_text:notosansjp-medium.otf_55:%25E3%2580%2590Flutter%25E3%2580%2591%25E5%25AE%259F%25E5%258B%2599%25E3%2581%25A7%25E4%25BD%25BF%25E3%2581%2586MVVM%25E3%2581%25AE%25E5%25AE%259F%25E8%25A3%2585%25E4%25BE%258B%2Cw_1010%2Cx_90%2Cy_100/g_south_west%2Cl_text:notosansjp-medium.otf_37:Kisia%2Cx_203%2Cy_121/g_south_west%2Ch_90%2Cl_fetch:aHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL3plbm4tdXNlci11cGxvYWQvYXZhdGFyLzJkNDkwODA1MzkuanBlZw==%2Cr_max%2Cw_90%2Cx_87%2Cy_95/v1627283836/default/og-base-w1200-v2.png)
![](https://terupro.net/wp-content/uploads/2022/05/undraw_My_universe_803e.png)
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fcdn.qiita.com%2Fassets%2Fpublic%2Fadvent-calendar-ogp-background-7940cd1c8db80a7ec40711d90f43539e.jpg?ixlib=rb-4.0.0&w=1200&mark64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTk3MiZoPTM3OCZ0eHQ9Rmx1dHRlciVFMyU4MSVBNyVFNiVBQyVBMSVFMyU4MSVBRSVFMyU4MyVBQyVFMyU4MyU5OSVFMyU4MyVBQiVFMyU4MSVCOCVFRiVCQyU4MSVFNCVCOCVBRCVFNyVCNCU5QSVFOCU4MCU4NSVFNSU5MCU5MSVFMyU4MSU5MVJpdmVycG9kJTIwTm90aWZpZXJQcm92aWRlciVFMyU4MSVBRSVFNCVCRCVCRiVFMyU4MSU4NCVFNiU5NiVCOSZ0eHQtY29sb3I9JTIzM0EzQzNDJnR4dC1mb250PUhpcmFnaW5vJTIwU2FucyUyMFc2JnR4dC1zaXplPTU2JnR4dC1hbGlnbj1sZWZ0JTJDdG9wJnM9OGFjODlmMmIwYjg1M2FjODFjZTk4Yjc2OTcxY2Y3YzA&mark-x=142&mark-y=100&blend64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZoPTc2Jnc9OTcyJnR4dD0lNDBEaWVnb0hvbmRhJnR4dC1jb2xvcj0lMjMzQTNDM0MmdHh0LWZvbnQ9SGlyYWdpbm8lMjBTYW5zJTIwVzYmdHh0LXNpemU9MzYmdHh0LWFsaWduPWxlZnQlMkN0b3Amcz00MTI1NGQ1M2EyNjBlNzA3MWRiZDg3Y2U1YmE0YWE3Zg&blend-x=142&blend-y=455&blend-mode=normal&txt64=aW4g5qCq5byP5Lya56S-dml2aU9O&txt-width=972&txt-clip=end%2Cellipsis&txt-color=%233A3C3C&txt-font=Hiragino%20Sans%20W6&txt-size=36&txt-x=156&txt-y=550&s=ff47d5880b3c7a98f8f83dd36387feeb)
![](https://res.cloudinary.com/zenn/image/upload/s--w8A8waLk--/c_fit%2Cg_north_west%2Cl_text:notosansjp-medium.otf_55:%25E3%2580%2590Flutter%25E3%2580%2591Riverpod%25202.0%2520%25E3%2581%25AE%2520Notifier%2520%25E3%2581%25A8%2520riverpod_generator%2520%25E3%2581%25AE%25E8%25A7%25A3%25E8%25AA%25AC%2Cw_1010%2Cx_90%2Cy_100/g_south_west%2Cl_text:notosansjp-medium.otf_37:futo%2Cx_203%2Cy_121/g_south_west%2Ch_90%2Cl_fetch:aHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL3plbm4tdXNlci11cGxvYWQvYXZhdGFyL2U3ZjgwMzlkODguanBlZw==%2Cr_max%2Cw_90%2Cx_87%2Cy_95/v1627283836/default/og-base-w1200-v2.png)
コメント