完全自分用の学習備忘録。
詳細な解説等はコード内でコメントアウトしております。
認証画面用とToDoリスト画面用のViewModel、それぞれのAPIエンドポイントにリクエストする情報の組み立て処理を実装していきます。
ViewModelを作成
以下のViewModelを作成する。
-
AuthViewModel
-
ToDoViewModel
-
lib_providers/auth_view_model.dart
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:spotify_app/api/requests/post/auth/sign_in_request.dart'; import 'package:spotify_app/api/requests/post/auth/sign_up_request.dart'; import 'package:spotify_app/api/service/api_service.dart'; import 'package:spotify_app/api/service/common_http_router.dart'; import 'package:spotify_app/model/auth_model.dart'; import 'package:spotify_app/model/custom_exception.dart'; import 'package:spotify_app/config/config.dart'; import 'package:spotify_app/config/storage.dart'; part 'auth_view_model.g.dart'; // ユーザー認証のロジックを管理するViewModel @riverpod class AuthViewModel extends _$AuthViewModel { // APIリクエストを実行するためのサービスクラス final ApiService _apiService = ApiService(); @override Future<bool> build() async { // 初期状態では認証が完了していないため、falseを返す return false; } // 共通の認証リクエスト処理 //`CommonHttpRouter`型のリクエストを受け取り、APIを呼び出す // 成功時: バックエンド(Golang)から受け取ったJWT認証トークンを端末のローカル上に保存し、stateに認証完了(true)を設定して、画面遷移 // 失敗時: stateにエラー(カスタム例外クラス)を設定して、エラー状態にする(エラーアラートを表示) Future<void> _handleAuthRequest(CommonHttpRouter request) async { // 状態をローディング中に設定 state = const AsyncValue.loading(); // APIリクエスト実行 final result = await _apiService(request); // Result型でリクエスト結果をハンドリング result.when( success: (Map<String, dynamic>? json) async { // JSONデータをAuthModelに変換 final authModel = AuthModel.fromJson(json!); // JWTトークンをセキュアストレージに保存 await SecureStorage().save(Config.secureStorageJwtTokenKey, authModel.jwtToken ?? ''); // 認証成功として状態を更新 state = const AsyncValue.data(true); }, exception: (CustomException error) { // エラーの場合、状態をエラーとして更新 state = AsyncError(error, StackTrace.empty); } ); } // サインアップ Future<void> signUp(String username, String email, String password) async { final authModel = AuthModel(username: username, email: email, password: password); // リクエストする情報を組み立てる final signUpRequest = SignUpRequest(authModel); _handleAuthRequest(signUpRequest); } // サインイン Future<void> signIn(String email, String password) async { final authModel = AuthModel(email: email, password: password); // リクエストする情報を組み立てる final signInRequest = SignInRequest(authModel); _handleAuthRequest(signInRequest); } }
-
lib_providers/to_do_view_model.dart
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:spotify_app/api/requests/post/todo/add_to_do_request.dart'; import 'package:spotify_app/api/requests/post/todo/delete_to_do_request.dart'; import 'package:spotify_app/api/requests/post/todo/fetch_to_dos_request.dart'; import 'package:spotify_app/api/requests/post/todo/update_to_do_request.dart'; import 'package:spotify_app/model/custom_exception.dart'; import 'package:spotify_app/model/to_do_model.dart'; import '../api/service/api_service.dart'; part 'to_do_view_model.g.dart'; @riverpod class ToDoViewModel extends _$ToDoViewModel { final ApiService _apiService = ApiService(); @override Future<List<ToDoModel>> build() async { await fetchToDos(); // 初回取得 return state.value ?? []; } // ToDoを取得 Future<void> fetchToDos() async { state = const AsyncValue.loading(); final fetchToDosRequest = FetchToDosRequest(); final result = await _apiService(fetchToDosRequest); result.when( success: (Map<String, dynamic>? json) async { final List<dynamic> todosJson = json!['todos']; final List<ToDoModel> todos = todosJson .map((todoJson) => ToDoModel.fromJson(todoJson as Map<String, dynamic>)) .toList(); state = AsyncValue.data(todos); }, exception: (CustomException error) { state = AsyncError(error, StackTrace.empty); } ); } // ToDoを新規作成 Future<void> addTodo(String title) async { state = const AsyncValue.loading(); final addToDoRequest = AddToDoRequest(title); final result = await _apiService(addToDoRequest); result.when( success: (Map<String, dynamic>? json) async { final newToDo = ToDoModel.fromJson(json!); final currentList = state.value ?? []; final updatedList = [...currentList, newToDo]; state = AsyncValue.data(updatedList); }, exception: (CustomException error) { state = AsyncError(error, StackTrace.empty); } ); } // ToDoを削除 Future<void> removeTodo(int id) async { state = const AsyncValue.loading(); final deleteToDoRequest = DeleteToDoRequest(id); final result = await _apiService(deleteToDoRequest); result.when( success: (Map<String, dynamic>? _) async { final updatedList = state.value?.where((todo) => todo.id != id).toList() ?? []; state = AsyncValue.data(updatedList); }, exception: (CustomException error) { state = AsyncError(error, StackTrace.empty); } ); } // ToDoを更新 Future<void> updateTodo(int id, String newTitle, bool isCompleted) async { state = const AsyncValue.loading(); final toDoModel = ToDoModel(id: id, title: newTitle, isCompleted: isCompleted); final updateToDoRequest = UpdateToDoRequest(toDoModel); final result = await _apiService(updateToDoRequest); result.when( success: (Map<String, dynamic>? _) async { final currentList = state.value ?? []; final updatedList = currentList.map((todo) { return todo.id == id ? todo.copyWith(title: newTitle, isCompleted: isCompleted) : todo; }).toList(); state = AsyncValue.data(updatedList); }, exception: (CustomException error) { state = AsyncError(error, StackTrace.empty); } ); } }
リクエストする情報を組み立てる
認証(サインアップ / サインイン)
-
lib/api/requests/sign_up_request.dart
import 'package:spotify_app/api/service/api_url.dart'; import 'package:spotify_app/api/service/common_http_router.dart'; import 'package:spotify_app/model/auth_model.dart'; import '../../../service/http_method.dart'; class SignUpRequest extends CommonHttpRouter { final AuthModel model; SignUpRequest(this.model); @override String get path => ApiUrl.signUp; @override HttpMethod get method => HttpMethod.POST; @override Object? body() => AuthModel.toJson(model); }
-
lib/api/requests/sign_in_request.dart
import 'package:spotify_app/api/service/api_url.dart'; import 'package:spotify_app/api/service/common_http_router.dart'; import 'package:spotify_app/model/auth_model.dart'; import '../../../service/http_method.dart'; class SignInRequest extends CommonHttpRouter { final AuthModel model; SignInRequest(this.model); @override String get path => ApiUrl.signIn; @override HttpMethod get method => HttpMethod.POST; @override Object? body() => AuthModel.toJson(model); }
Todo(新規作成 / 更新 / 全件取得 / 削除)
-
lib/api/requests/add_to_do_request.dart
import 'dart:convert'; import 'package:spotify_app/api/service/api_url.dart'; import 'package:spotify_app/api/service/common_http_router.dart'; import '../../../service/http_method.dart'; class AddToDoRequest extends CommonHttpRouter { final String title ; AddToDoRequest(this.title); @override String get path => ApiUrl.addToDo; @override HttpMethod get method => HttpMethod.POST; @override Object? body() => {'title': title}; }
-
lib/api/requests/create_to_do_request.dart
import 'package:spotify_app/api/service/api_url.dart'; import 'package:spotify_app/api/service/common_http_router.dart'; import 'package:spotify_app/model/to_do_model.dart'; import '../../../service/http_method.dart'; class UpdateToDoRequest extends CommonHttpRouter { final ToDoModel model; UpdateToDoRequest(this.model); @override String get path => ApiUrl.editToDo; @override HttpMethod get method => HttpMethod.PUT; @override List<String>? get pathParameters => [model.id.toString()]; @override Object? body() => ToDoModel.toJson(model); }
-
lib/api/requests/fetch_to_dos_request.dart
import 'package:spotify_app/api/service/api_url.dart'; import 'package:spotify_app/api/service/common_http_router.dart'; import 'package:spotify_app/model/to_do_model.dart'; import '../../../service/http_method.dart'; class FetchToDosRequest extends CommonHttpRouter { @override String get path => ApiUrl.fetchToDos; @override HttpMethod get method => HttpMethod.GET; }
-
lib/api/requests/delete_to_do_reqeust.dart
import 'package:spotify_app/api/service/api_url.dart'; import 'package:spotify_app/api/service/common_http_router.dart'; import 'package:spotify_app/model/to_do_model.dart'; import '../../../service/http_method.dart'; class DeleteToDoRequest extends CommonHttpRouter { final int toDoId; DeleteToDoRequest(this.toDoId); @override String get path => ApiUrl.deleteToDo; @override HttpMethod get method => HttpMethod.DELETE; @override List<String>? get pathParameters => [toDoId.toString()]; }