화면 파괴 시 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