[플러터] 24. MVVM 패턴 - (5) 게시글 수정, 추가

KangHo Lee's avatar
Jan 07, 2025
[플러터] 24. MVVM 패턴 - (5) 게시글 수정, 추가

1. post_repository.dart

class PostRepository { const PostRepository(); Future<Map<String, dynamic>> add(Map<String, dynamic> data) async { Response response = await dio.post("/api/post", data: data); Map<String, dynamic> body = response.data; return body; } Future<Map<String, dynamic>> update(int id, Map<String, dynamic> data) async { Response response = await dio.put("/api/post/${id}", data: data); Map<String, dynamic> body = response.data; return body; } }
  • 메서드 작성은 API 문서를 참고했습니다.
notion image
notion image

2. PostUpdateForm

class PostUpdateForm extends ConsumerWidget { final _formKey = GlobalKey<FormState>(); final _title = TextEditingController(); final _content = TextEditingController(); Post post; PostUpdateForm(this.post); @override Widget build(BuildContext context, WidgetRef ref) { PostDetailVM vm = ref.read(postDetailProvider(post.id!).notifier); return Form( key: _formKey, child: ListView( children: [ CustomTextFormField( controller: _title, initValue: "${post.title}", hint: "Title", ), const SizedBox(height: smallGap), CustomTextArea( controller: _content, initValue: "${post.content}", hint: "Content", ), const SizedBox(height: largeGap), CustomElevatedButton( text: "글 수정하기", click: () { // 1. 사용자 입력값 받기 // 2. 유효성검사 (생략) // 3. VM에게 위임 vm.update(post.id!, _title.text.trim(), _content.text.trim()); }, ), ], ), ); } }

3. PostDetailVM

class PostDetailModel { Post post; PostDetailModel({required this.post}); PostDetailModel copyWith({Post? post}) { return PostDetailModel(post: post ?? this.post); } PostDetailModel.fromMap(Map<String, dynamic> map) : post = Post.fromMap(map); } final postDetailProvider = NotifierProvider.family .autoDispose<PostDetailVM, PostDetailModel?, int>(() { return PostDetailVM(); }); class PostDetailVM extends AutoDisposeFamilyNotifier<PostDetailModel?, int> { final mContext = navigatorKey.currentContext!; PostRepository postRepository = const PostRepository(); @override PostDetailModel? build(id) { init(id); return null; } Future<void> update(int id, String title, String content) async { final requestBody = { "title": title, "content": content, }; Map<String, dynamic> responseBody = await postRepository.update(id, requestBody); if (!responseBody["success"]) { ScaffoldMessenger.of(mContext!).showSnackBar( SnackBar(content: Text("게시글 수정 실패 : ${responseBody["errorMessage"]}")), ); return; } state = PostDetailModel.fromMap(responseBody["response"]); ref .read(postListProvider.notifier) .update(Post.fromMap(responseBody["response"])); // 수정 중이던 게시글로 이동 Navigator.pop(mContext); }
  • update의 경우 PostDetailVM의 상태(state)와 PostListVM의 상태 모두 갱신해야 합니다.

4. PostWriteForm

class PostWriteForm extends ConsumerWidget { final _formKey = GlobalKey<FormState>(); final _title = TextEditingController(); final _content = TextEditingController(); PostWriteForm({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { PostListVM vm = ref.read(postListProvider.notifier); return Form( key: _formKey, child: ListView( shrinkWrap: true, children: [ CustomTextFormField( controller: _title, hint: "Title", ), const SizedBox(height: smallGap), CustomTextArea( controller: _content, hint: "Content", ), const SizedBox(height: largeGap), CustomElevatedButton( text: "글쓰기", click: () { // 1. 사용자 입력값 받기 // 2. 유효성검사 (생략) // 3. VM에게 위임 vm.add(_title.text.trim(), _content.text.trim()); }, ), ], ), ); } }
  • postDetailProvider는 매개변수로 postId를 받아야 하기 때문에 postListProvider를 호출했습니다.

5. PostListVM

class PostListModel { // 생략 final postListProvider = NotifierProvider<PostListVM, PostListModel?>(() { return PostListVM(); }); class PostListVM extends Notifier<PostListModel?> { final refreshCtrl = RefreshController(); final mContext = navigatorKey.currentContext!; PostRepository postRepository = const PostRepository(); // build 생략 void add(String title, String content) async { final requestBody = { "title": title, "content": content, }; Map<String, dynamic> responseBody = await postRepository.add(requestBody); if (!responseBody["success"]) { ScaffoldMessenger.of(mContext!).showSnackBar( SnackBar(content: Text("글쓰기 실패 : ${responseBody["errorMessage"]}")), ); return; } Post post = Post.fromMap(responseBody["response"]); // 이전 상태 받아오기 PostListModel model = state!; // 깊은 복사, 새 post가 앞으로 model.posts = [post, ...model.posts]; // 상태 갱신 state = state!.copyWith(posts: model.posts); // postList 페이지로 이동 Navigator.pop(mContext); } void update(Post post) { PostListModel model = state!; // 기존 posts 리스트를 순회하면서 id가 일치하는 경우 업데이트된 post로 교체 model.posts = model.posts.map((p) => p.id == post.id ? post : p).toList(); // postList 상태 갱신 state = state!.copyWith(posts: model.posts); } }
  • ref.read(postListProvider.notifier).update(Post.fromMap(responseBody["response"]));
    • PostDetailVM 에서 호출하는 update 메서드를 여기에 적습니다.
 
Share article

devleekangho