상태변경 (수정, 삭제)

김인범's avatar
Jan 18, 2025
상태변경 (수정, 삭제)
📌

삭제 (화면이동을 할 경우)

화면을 POP 하더라도 VM은 살아있는 상태이다.
그러므로 화면이 파괴될 때, VM도 파괴할 수 있도록 코드를 짜야한다.
화면 이동을 한 다음 이동한 화면의 VM을 통해 상태가 변경되었다는 것을 인지시켜야 한다.
 

화면 파괴 시 VM도 파괴되도록 설정하는 방법

NotifierProvider 의 family ⇒ autoDispose를 사용한다.
final postDetailProvider = NotifierProvider.family .autoDispose<PostDetailVM, PostDetailModel?, int>(() { return PostDetailVM(); });
 

상태변경이 일어나는 비즈니스 실행 시 변화를 인지시키는 법

게시글 삭제를 예로 진행하겠습니다.
class PostDetailButtons extends ConsumerWidget { Post post; PostDetailButtons(this.post); @override Widget build(BuildContext context, WidgetRef ref) { SessionUser sessionUser = ref.read(sessionProvider); // postDetailProvider <- family 일 때는 1번으로 창고가 만들어지면, 다시 1번을 창고 만들면 SingleTone PostDetailVM vm = ref.read(postDetailProvider(post.id!).notifier); if (sessionUser.id != post.user!.id!) { return SizedBox(); } else { return Row( mainAxisAlignment: MainAxisAlignment.end, children: [ IconButton( onPressed: () { vm.deleteById(post.id!); }, icon: const Icon(CupertinoIcons.delete), ), IconButton( onPressed: () { Navigator.push(context, MaterialPageRoute(builder: (_) => PostUpdatePage(post))); }, icon: const Icon(CupertinoIcons.pen), ), ], ); } } }
vm.deleteById(post.id!); 를 통해서 VM의 remove함수를 실행 시킵니다.
Future<void> deleteById(int id) async { Map<String, dynamic> responseBody = await postRepo.delete(id); if (!responseBody["success"]) { ScaffoldMessenger.of(mContext!).showSnackBar( SnackBar(content: Text("게시글 삭제 실패 : ${responseBody["errorMessage"]}")), ); return; } // PostListVM 상태 변경 // 화면 파괴 시 autoDispose 됨 Navigator.pop(mContext); }
postRepo.delete(id); 를 통해 통신을 하고, 해당 화면을 POP하는 것이 아니라
요청을 받은 다음 PostLIstVm 의 상태를 변경해야합니다.
그래야 목록 화면에 삭제한 게시글이 남아있지 않도록 만들기 때문입니다.
 
통신이 오고난 뒤
ref.read(postListProvider.notifier).init(0); 을 사용하는 방법이 있지만
해당 방법은 init이 실행되고 통신을 하게 되기 때문에
불필요한 통신이 발생하게 되어 좋은 방법은 아닙니다.
 
대신
ref.read(postListProvider.notifier).remove(id); 를 통해서
PostListVm에 remove 메서드를 만들어 주어서 변화를 인지시킬 수 있습니다.
void remove(int id) { PostListModel model = state!; model.posts = model.posts.where((p) => p.id != id).toList(); state = state!.copyWith(posts: model.posts); }
postlistVm에서는 copyWith 함수를 만들어 주어야 합니다.
해당 메서드는
PostListModel copyWith( {bool? isFirst, bool? isLast, int? pageNumber, int? size, int? totalPage, List<Post>? posts}) { return PostListModel( isFirst: isFirst ?? this.isFirst, isLast: isLast ?? this.isLast, pageNumber: pageNumber ?? this.pageNumber, size: size ?? this.size, totalPage: totalPage ?? this.totalPage, posts: posts ?? this.posts); }
위와 같은 형태로 생성자를 만들어서
변경점이 없는 것들은 Null로 들어오기 때문에 null인 것들은 기존의 데이터를 유지시키고,
변경점이 있는 것은 생성될 때 받은
EX) state!.copyWith(posts: model.posts); 를 통해서 posts만 바뀌게 됩니다.
 
이런 식으로 copyWith를 통해
void remove(int id) { PostListModel model = state!; //현재 상태 model.posts = model.posts.where((p) => p.id != id).toList(); // 삭제된 아이디 제외한 목록들 다시 리스트로 만들고 그것을 model의 posts에 넣어준다. // copyWith 생성자를 통해서 변경된 posts만 받았기에 상태가 변한 것을 인지하고, // list페이지에 적용시킨다. state = state!.copyWith(posts: model.posts); }
상태 변화를 인지 시킬 수 있습니다.
 
게시글 추가, 게시글 수정에도 적용가능 합니다.

전체코드

postdetailVm < postlistVm의 copyWith 호출 (상태변화 인지 시킨다.)
import 'package:flutter/material.dart'; import 'package:flutter_blog/data/model/post.dart'; import 'package:flutter_blog/data/repository/post_repository.dart'; import 'package:flutter_blog/ui/pages/post/list_page/post_list_vm.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../main.dart'; class PostDetailModel { Post post; PostDetailModel.fromMap(Map<String, dynamic> map) : post = Post.fromMap(map); } // 제너릭 세번째에 넣어주면 값을 넘겨받을 수 있다. final postDetailProvider = NotifierProvider.family .autoDispose<PostDetailVM, PostDetailModel?, int>(() { return PostDetailVM(); }); // Notifier -> FamilyNotifier 변경 class PostDetailVM extends AutoDisposeFamilyNotifier<PostDetailModel?, int> { final mContext = navigatorKey.currentContext!; PostRepository postRepo = const PostRepository(); @override PostDetailModel? build(id) { init(id); return null; } Future<void> init(int id) async { Map<String, dynamic> responseBody = await postRepo.findById(id); if (!responseBody["success"]) { ScaffoldMessenger.of(mContext!).showSnackBar( SnackBar( content: Text("게시글 상세보기 실패 : ${responseBody["errorMessage"]}")), ); return; } PostDetailModel model = PostDetailModel.fromMap(responseBody["response"]); state = model; } Future<void> deleteById(int id) async { Map<String, dynamic> responseBody = await postRepo.delete(id); if (!responseBody["success"]) { ScaffoldMessenger.of(mContext!).showSnackBar( SnackBar(content: Text("게시글 삭제 실패 : ${responseBody["errorMessage"]}")), ); return; } // PostListVM 상태 변경 // ref.read(postListProvider.notifier).init(0); 통신을 다시 하는 것, 상태관리 XX ref.read(postListProvider.notifier).remove(id); // 화면 파괴 시 autoDispose 됨 Navigator.pop(mContext); } }
 
postlistVm ← copyWith 들고 있음
import 'package:flutter/material.dart'; import 'package:flutter_blog/data/repository/post_repository.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../data/model/post.dart'; import '../../../../main.dart'; class PostListModel { bool isFirst; bool isLast; int pageNumber; int size; int totalPage; List<Post> posts; PostListModel( {required this.isFirst, required this.isLast, required this.pageNumber, required this.size, required this.totalPage, required this.posts}); PostListModel copyWith( {bool? isFirst, bool? isLast, int? pageNumber, int? size, int? totalPage, List<Post>? posts}) { return PostListModel( isFirst: isFirst ?? this.isFirst, isLast: isLast ?? this.isLast, pageNumber: pageNumber ?? this.pageNumber, size: size ?? this.size, totalPage: totalPage ?? this.totalPage, posts: posts ?? this.posts); } PostListModel.fromMap(Map<String, dynamic> map) : isFirst = map["isFirst"], isLast = map["isLast"], pageNumber = map["pageNumber"], size = map["size"], totalPage = map["totalPage"], posts = (map["posts"] as List<dynamic>) .map((e) => Post.fromMap(e)) .toList(); // 묵시적 형변환 List<dynamic>으로 list타입이 된다. & map<String,dynamic>으로 받게된다. 이걸 Post.fromMap으로 하나씩 리스트에 넣는다. } class PostList_Post {} class PostList_User {} final postListProvider = NotifierProvider<PostListVM, PostListModel?>(() { return PostListVM(); }); class PostListVM extends Notifier<PostListModel?> { final mContext = navigatorKey.currentContext!; PostRepository postRepo = const PostRepository(); @override PostListModel? build() { init(0); return null; } Future<void> init(int page) async { Map<String, dynamic> responseBody = await postRepo.findAll(page: page); if (!responseBody["success"]) { ScaffoldMessenger.of(mContext!).showSnackBar( SnackBar( content: Text("게시글 목록보기 실패 : ${responseBody["errorMessage"]}")), ); return; } state = PostListModel.fromMap(responseBody["response"]); } void remove(int id) { PostListModel model = state!; model.posts = model.posts.where((p) => p.id != id).toList(); state = state!.copyWith(posts: model.posts); } void add(Post post) { PostListModel model = state!; model.posts = [post, ...model.posts]; state = state!.copyWith(posts: model.posts); } }
 
Share article

taker