게시글 목록 불러오기

전체적인 진행 로직, 통신, model 사용
김인범's avatar
Jan 18, 2025
게시글 목록 불러오기
게시글 목록을 불러오겠습니다.

post_list_vm 만들기

게시글 목록 페이지의 View Model을 만들어줘야 합니다.
final postListProvider = NotifierProvider<PostListVM, PostListModel?>(() { return PostListVM(); }); class PostListVM extends Notifier<PostListModel?> { @override PostListModel? build() { init(); return null; } Future<void> init() async { } }
 
만들 때 해당 페이지에서만 사용하는 model이기 때문에 생성자를 만들어줘야 합니다.
class PostListModel { bool isFirst; bool isLast; int pageNumber; int size; int totalPage; List<Post> posts; }
초기에는 null값을 받도록 만들어 줄 수도 있습니다.
 

하는 김에 Post, User class 만들기

class User { int? id; String? username; String? imgUrl; User.fromMap(Map<String, dynamic> map) : this.id = map["id"], this.username = map["username"], this.imgUrl = map["imgUrl"]; }
class Post { int? id; String? title; String? content; DateTime? createdAt; DateTime? updatedAt; int? bookmarkCount; bool? isBookmark; User? user; Post.fromMap(Map<String, dynamic> map) : id = map["id"], title = map["title"], content = map["content"], createdAt = DateFormat("yyyy-mm-dd").parse(map["createdAt"]), updatedAt = DateFormat("yyyy-mm-dd").parse(map["createdAt"]), bookmarkCount = map["bookmarkCount"], isBookmark = map["isBookmark"], user = User.fromMap(map["user"]); }
.fromMap 메서드가 있습니다.
이 메서드는 응답받은 데이터를 받아서 ViewModel로 사용할 수 있도록 변경해주는 메서드입니다.
 

딴길로 샛다. 다시 통신으로 진행하자

post_list_vm에서 build가 실행 될 때,
init이 실행 됩니다. 초기에 실행되면서 init을 통해 repostitory에 통신을 요청합니다.
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"]); }
 

통신에서 (repository)

class PostRepository { const PostRepository(); //Map<String, dynamic> Future<Map<String, dynamic>> findAll({int page = 0}) async { Response response = await dio.get("/api/post", queryParameters: {"page": page}); Map<String, dynamic> body = response.data; return body; } }
문서를 보면 게시글목록을 요청할 때는
notion image
파라미터를 요구하고 있습니다.
이때는 dio의 요청을 보낼 때 queryParameters를 적어줄 수 있습니다.
await dio.get("/api/post", queryParameters: {"page": page}); <<
 

통신이 끝나고 응답이 오면

응답이 오면 VM에서 후 처리를 진행합니다.
응답 상태가 불량이면
if (!responseBody["success"]) { ScaffoldMessenger.of(mContext!).showSnackBar( SnackBar( content: Text("게시글 목록보기 실패 : ${responseBody["errorMessage"]}")), ); return; }
게시글 목록보기 실패를 띄워줍니다.
 
성공한다면
state = PostListModel.fromMap(responseBody["response"]);
만들어 두었던 fromMap을 사용하여 상태를 변경시켜 줍니다.
화면에서는 ref.watch를 통해 상태를 관찰하고 있기 때문에 자동으로 변경이 됩니다.
 

ViewModel 전체코드

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.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"]); } }

View 전체코드

import 'package:flutter/material.dart'; import 'package:flutter_blog/ui/pages/post/detail_page/post_detail_page.dart'; import 'package:flutter_blog/ui/pages/post/list_page/post_list_vm.dart'; import 'package:flutter_blog/ui/pages/post/list_page/wiegets/post_list_item.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class PostListBody extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { PostListModel? model = ref.watch(postListProvider); if (model == null) { return Center(child: CircularProgressIndicator()); } else { return ListView.separated( itemCount: model.posts.length, itemBuilder: (context, index) { return InkWell( onTap: () { Navigator.push( context, MaterialPageRoute(builder: (_) => PostDetailPage())); }, child: PostListItem(model.posts[index]), ); }, separatorBuilder: (context, index) { return const Divider(); }, ); } } }
Share article

taker