Search

#024 #페이징

페이징은 덮어씌우는 것이 아니라 추가해야된다.
안드로이드 액티비티 생명주기 공부
플러터 생명주기 공부해야됨

currentOffset의 maxScrollExtent가 발생했을 때 캐치

해당 함수를 비동기로 돌려야 된다. await

리프레시 컨트롤러 컴플리트를 호출해야 인디케이터가 종료된다.

마지막 페이지일 경우에 약간의 통신하는 것처럼 만들어준다.

통신한 데이터로 더 그려주기

전개 연산자를 사용하면 간단하게 구현이 가능하다.
import 'package:flutter/material.dart'; import 'package:flutter_blog/ui/pages/post/list_page/post_list_viewmodel.dart'; import 'package:flutter_blog/ui/pages/post/list_page/wiegets/post_list_body.dart'; import 'package:flutter_blog/ui/widgets/custom_navigator.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class PostListPage extends ConsumerWidget { final scaffoldKey = GlobalKey<ScaffoldState>(); final refreshKey = GlobalKey<RefreshIndicatorState>(); PostListPage({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { return Scaffold( key: scaffoldKey, drawer: CustomNavigation(scaffoldKey), appBar: AppBar( title: Text("Blog"), ), body: PostListBody(), ); } }
Java
복사
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_viewmodel.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'post_list_item.dart'; // 이 화면 빌드될 때, 창고, 창고데이터, 창고관리자 생성되어야 한다. class PostListBody extends ConsumerWidget { const PostListBody({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { PostListModel? model = ref.watch(postListProvider); PostListViewModel viewModel = ref.read(postListProvider.notifier); if (model == null) { return CircularProgressIndicator(); } else { return SmartRefresher( controller: viewModel.refreshCtrl, enablePullDown: true, enablePullUp: true, onRefresh: () async => await viewModel.notifyInit(0), onLoading: () async => await viewModel.nextList(), child: ListView.separated( itemCount: model.posts.length, itemBuilder: (context, index) { return InkWell( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (_) => PostDetailPage(model.posts[index].id))); }, child: PostListItem(model.posts[index]), ); }, separatorBuilder: (context, index) { return const Divider(); }, ), ); } } }
Java
복사
import 'package:flutter/material.dart'; import 'package:flutter_blog/data/dtos/paging_dto.dart'; import 'package:flutter_blog/data/dtos/post_request.dart'; import 'package:flutter_blog/data/dtos/response_dto.dart'; import 'package:flutter_blog/data/models/post.dart'; import 'package:flutter_blog/data/repositories/post_repository.dart'; import 'package:flutter_blog/data/store/session_store.dart'; import 'package:flutter_blog/main.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logger/logger.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; // 창고 데이터 class PostListModel { PageDTO page; List<Post> posts; PostListModel({required this.page, required this.posts}); } // 창고 class PostListViewModel extends StateNotifier<PostListModel?> { // 1번 리플래시 컨트롤러 등록 final refreshCtrl = RefreshController(); final mContext = navigatorKey.currentContext; final Ref ref; PostListViewModel(super.state, this.ref); // 로드함수 (1, 2) Future<void> notifyInit(int page) async { SessionStore sessionStore = ref.read(sessionProvider); String jwt = sessionStore.accessToken!; ResponseDTO responseDTO = await PostRepository().fetchPostList(jwt, page: page); if (responseDTO.success) { PostListModel nextModel = responseDTO.response; if (page > 0) { PostListModel prevModel = state!; nextModel.posts = [...prevModel.posts, ...nextModel.posts]; state = nextModel; } else { state = nextModel; } } else { ScaffoldMessenger.of(mContext!).showSnackBar( SnackBar(content: Text("게시물 보기 실패 : ${responseDTO.errorMessage}"))); } // 2. 이벤트 종료시에 리플래시 종료 (최초 로드, 풀다운) refreshCtrl.refreshCompleted(); } Future<void> notifyAdd(PostSaveReqDTO reqDTO) async { SessionUser sessionUser = ref.read(sessionProvider); ResponseDTO responseDTO = await PostRepository().savePost(reqDTO, sessionUser.accessToken!); if (responseDTO.success) { Post newPost = responseDTO.response; List<Post> prevPosts = state!.posts; PageDTO pageDTO = state!.page; // DESC 넣기 List<Post> newPosts = [newPost, ...prevPosts]; state = PostListModel(page: pageDTO, posts: newPosts); Navigator.pop(mContext!); } else { ScaffoldMessenger.of(mContext!).showSnackBar( SnackBar(content: Text("게시물 작성 실패 : ${responseDTO.errorMessage}")), ); } } // 통신없이 상태 변경 void deletePost(int postId) { PostListModel model = state!; List<Post> prevPosts = model.posts; PageDTO prevPage = model.page; List<Post> newPosts = prevPosts.where((p) => p.id != postId).toList(); state = PostListModel(posts: newPosts, page: prevPage); } Future<void> updatePost(Post post) async { // 1. 기존 값 가지고 오기 PostListModel model = state!; // 2. 부분 업데이트 PageDTO prevPage = model.page; List<Post> prevPosts = model.posts; // 자바 ()->{}, 자바스크립트 ()=>{}, 다트 (){} List<Post> newPosts = prevPosts.map((p) { if (p.id == post.id) { return post; } else { return p; } }).toList(); await Future.delayed( Duration( seconds: 5, ), () => {}); state = PostListModel(page: prevPage, posts: newPosts); // 통신코드 } Future<void> nextList() async { PageDTO pageDTO = state!.page; if (pageDTO.isLast) { await Future.delayed(Duration(microseconds: 500)); refreshCtrl.loadComplete(); return; } Logger().d("다음페이지 로드 : ${pageDTO.pageNumber + 1}"); await notifyInit(pageDTO.pageNumber + 1); refreshCtrl.loadComplete(); } } // 창고 관리자 final postListProvider = StateNotifierProvider<PostListViewModel, PostListModel?>((ref) { return PostListViewModel(null, ref)..notifyInit(0); });
Java
복사