■MVP
->プレゼンターが能動的にViewを更新しに行く
- Model = ビジネスロジック+データアクセス
- Presenter = アプリケーションロジック+Viewの更新
- View = UI制御
■MVVM
->データバインディングでViewModelの値がViewに反映される
- Model = ビジネスロジック+データアクセス
- ViewModel = アプリケーションロジック
- View = UI制御
■Riverpod
- Provider = データの管理、提供者
-> ViewModelはProviderを使ってデータを操作 - Consumer:データの使用者
-> ViewはConsumerWidgetを継承してデータを監視しておくと、Providerからデータが渡ってくる(データバインディング)
■実装例
dart models/todo.dart
// Model
// データクラス
class Todo {
const Todo({required this.id, required this.title, this.isCompleted = false});
final String id;
final String title;
final bool isCompleted;
}
dart view_models/todos_view_model.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/todo.dart';
// ViewModel
// ProviderとViewModel
final todosViewModelProvider =
StateNotifierProvider<TodosViewModel, List<Todo>>((ref) {
return TodosViewModel();
});
class TodosViewModel extends StateNotifier<List<Todo>> {
// 初期状態として空のリストを追加
TodosViewModel() : super([]);
void addTodo(Todo todo) {
state = [...state, todo];
}
void addTodoByTitle(String title) {
addTodo(Todo(id: "$title${state.length + 1}", title: title));
}
void toggleTodoStatus(String id) {
// 完了フラグを反転させる
state = state.map((todo) {
if (todo.id == id) {
return Todo(
id: todo.id, title: todo.title, isCompleted: !todo.isCompleted);
}
return todo;
}).toList();
}
}
dart main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mvvm_riverpod_test/view_models/todos_view_model.dart';
import 'models/todo.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: TodosPage(),
);
}
}
class TodosPage extends ConsumerWidget {
TodosPage({super.key});
TextEditingController controller = TextEditingController();
@override
Widget build(BuildContext context, WidgetRef ref) {
// データの監視
List<Todo> todos = ref.watch(todosViewModelProvider);
return Scaffold(
appBar: AppBar(
title: const Text('TODO'),
),
body: ListView.builder(
// ListView.builderは以下のカウント数分ループ
itemCount: todos.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
// 一つ目の要素はテキストフィールド
return ListTile(
title: TextField(controller: controller),
trailing: ElevatedButton(
onPressed: () {
ref
.read(todosViewModelProvider.notifier)
.addTodoByTitle(controller.text);
controller.clear();
},
child: const Text('登録'),
),
);
}
final todo = todos[index - 1];
return ListTile(
title: Text(todo.title),
trailing: Checkbox(
value: todo.isCompleted,
onChanged: (bool? newValue) {
ref
.read(todosViewModelProvider.notifier)
.toggleTodoStatus(todo.id);
},
),
);
},
),
);
}
}