들어가기 앞서
Flutter로 무한 스크롤 구현을 보던중 GetX를 사용하는 법 등 다양하게 있었는데 Infinite_scroll_pagination 패키지를 활용한 레퍼런스는 많은데 번역 된 레퍼런스가 잘 없다보니 좀 많이 해맸다 그래서 내가 한 번 정리해보기로 했다
만들어볼 것
먼저 나는 pubspec.yaml에 밑에 처럼 설정해줬다
flutter:
sdk: flutter
flutter_secure_storage: ^7.0.1 //로컬 스토리지 활용
http: "0.13.5" //백엔드 API 연동!
infinite_scroll_pagination: ^3.2.0 //무한 스크롤 패키지
그 다음 할 일은 새로운 Dart파일을 하나 만들어 준다
일단 먼저 어떤 데이터들을 불러올지 정하자!
{
"content": [
{
"post_id": 79,
"content": "무한 스크롤",
"likes": 99,
"post_picture": 1번 사진,
"title": "무한 스클롤 라이브러리",
}
]
"page": ...
}
저는 위 코드에서 content, title 두 개만 가져올 예정입니다
그렇다면 위 JSON을 가져올 수 있도록 코드를 짜줍시다
class PostsList {
late final List<Post> posts;
PostsList({required this.posts});
factory PostsList.fromJson(Map<String, dynamic> parsedJson) {
var list = parsedJson['content'] as List;
List<Post> postList = list.map((i) => Post.fromJson(i)).toList();
return PostsList(posts: postList);
}
}
class Post {
final String title;
final String content;
Post({required this.title, required this.content});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(content: json['content'], title: json['title']);
}
}
Dart로 복잡한 JSON을 파싱하는 게 조금 어려웠습니다
저는 https://www.oowgnoj.dev/post/flutter-json/ 해당 글이 많이 도움됬습니다
Dart로 복잡한 JSON 파싱이 어렵다면 꼭 읽어보시길 추천드립니다
class InfiniteScrollPaginatorDemo extends StatefulWidget {
const InfiniteScrollPaginatorDemo({super.key});
@override
_InfiniteScrollPaginatorDemoState createState() =>
_InfiniteScrollPaginatorDemoState();
}
class _InfiniteScrollPaginatorDemoState
extends State<InfiniteScrollPaginatorDemo> {
final _numberOfPostsPerRequest = 5;
final PagingController<int, Post> _pagingController = //페이지 번호는 int형으로 받겠다
PagingController(firstPageKey: 0); //처음 페이지 설정
@override
void initState() {
_pagingController.addPageRequestListener((pageKey) {//페이지를 가져오는 리스너
_fetchPage(pageKey);
});
super.initState();
}
@override
void dispose() {
_pagingController.dispose();
super.dispose();
}
}
먼저 StatefulWidget으로 만들어줘야한다 왜냐하면 우리는 무한스크롤 즉 밑으로 내릴수록 화면상의 보이는 데이터들이 바뀌줘야 하기 때문이다 --> initState()가 있어야 하니까
PaingController에는 첫 번째 변수는 int 사용하려는 API 페이지 번호의 데이터 유형을 나타낸다 ex) int형을 선언해줬으니 페이지 번호는 1 , 2, 3 이런식이겠죠?
그리고 initState()에 페이지를 가져오는 리스너를 하나 선언해줍니다 -> 나중에 API에서 데이터를 가져와 업데이트 해줄 겁니다
Future<void> _fetchPage(int pageKey) async {
try {
final response = await get(
Uri.parse(
"http://localhost:8081/api/post/ //API에서 값을 가져옵니다
mywrite?start=$pageKey&show=$_numberOfPostsPerRequest"),
);
Map<String, dynamic> responseList2 = json.decode(response.body);
//Json으로 된 Date를 Map<String, dynamic>형식으로 저장합니다
List responseList = responseList2['content'];
//Json으로 받은 형식의 데이터 중 ['content']가 Key로된 값을 받았습니다
var result = PostsList.fromJson(responseList2);
//데이터 처리를 해줍니다
final isLastPage = result.posts.length < _numberOfPostsPerRequest;
//만약 조회한페이지보다 요청한 페이지가 많을 때 true, else false처리
//--> 결론 isLastPage는 마지막페이지면 True else False
await Future.delayed(const Duration(seconds: 1));
//무한스크롤시 데이터를 스크롤할 때마다 오는지 체크하기 위해서 딜레이를 1초정도 주었다
if (isLastPage) {
//더이상 페이지가 없을 때
_pagingController.appendLastPage(result.posts);
}
else {
//마지막 페이지가 아니라면
final nextPageKey = pageKey + 1; // 1페이지 더하기
_pagingController.appendPage(result.posts, nextPageKey);
}
} catch (e) {
print("error --> $e");
_pagingController.error = e;
}
}
제가 만든 API를 불러와서 데이터 처리를 해봤습니다
Http Pacakge를 활용해서 API데이터를 받아왔고 -> JSON을 사용할 수 있게 파싱한 뒤 -> 마지막페이지가 있는지 판단합니다
그리고 appendLastPage또는 메소드를 사용하여 생성된 목록을 저장합니다
이제 데이터를 정제했으니 화면에 출력해봅시다
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color.fromARGB(100, 30, 30, 30),
appBar: AppBar( //AppBar
title: const Text("무한 스크롤"),
titleTextStyle: const TextStyle(
color: Color.fromARGB(255, 255, 255, 255),
fontSize: 25,
fontWeight: FontWeight.w700,
),
centerTitle: true,
backgroundColor: const Color.fromARGB(100, 30, 30, 30),
),
body: RefreshIndicator( //새로고침 package안에 들어있는 키워드
onRefresh: () => Future.sync(() => _pagingController.refresh()),//새로고침시 초기화
child: PagedListView<int, Post>(
pagingController: _pagingController, //저장했던 정보들
builderDelegate: PagedChildBuilderDelegate<Post>(
itemBuilder: (context, item, index) => Padding(
padding: const EdgeInsets.all(15.0),
child: PostItem(item.title, item.content),
),
),
),
),
);
}
위에서 중요하게 볼 곳은 body 부분입니다
Refreshindicator를 사용하여 만약 새로고침을 한다면 데이터를 초기화하게 만들었고
Post로 된 ListView를 만들었습니다 데이터들은 _pagingController에 저장했던 데이터들을 가져오고
builderDelegate는 어떤 형식으로 화면에 그릴 것인지를 나타냅니다
PagedChildBuilderDelegate
무한 스크롤 페이지 매김과 관련된 모든 위젯에 대한 빌더 모음입니다. (Kodeco.com)
그럼 다음으로 itemBuilder를 사용하요 목록 항목을 구성합니다 (필수)
Post->Item이라고 생각하면 편하겠습니다
class PostItem extends StatelessWidget {
final String content;
final String title;
const PostItem(this.content, this.title, {super.key});
@override
Widget build(BuildContext context) {
return Container(
height: 400,
width: 100,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(11),
),
color: Color.fromARGB(255, 136, 203, 234),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
title,
style: const TextStyle(
color: Color.fromARGB(255, 255, 158, 249),
fontSize: 25,
fontWeight: FontWeight.bold),
),
const SizedBox(
height: 10,
),
Text(
content,
style: const TextStyle(
fontSize: 20,
color: Color.fromARGB(255, 253, 253, 253),
),
)
],
),
),
);
}
}
이렇게 PostItem을 활용해 화면에 뿌려주는 코드들을 짜줍시다
위에 주석을 참고하시면 이해가 되실 거라 생각합니다 PostItem은 특별한 건 없습니다
저렇게 데이터를 주고 하나하나 그리도록 해줍니다
그러면 PostItem에 전달된 값으로 이제 우리는 카드 모양의 List들을 화면에 보여줄 수 있습니다
안에 content, title이 없는 이유는 제 DB에 null값을 넣어둬서 그렇습니다!
참고한 레퍼런스
Dart로 복잡한 JSON파싱
https://www.oowgnoj.dev/post/flutter-json/
Flutter 무한 스크롤하는 방법들
https://blog.logrocket.com/implement-infinite-scroll-pagination-flutter/
'Flutter' 카테고리의 다른 글
[Flutter] DropDownButton 활용하기 (0) | 2023.02.01 |
---|---|
[Flutter] 20시간만에 Flutter 이해하기 (0) | 2023.01.15 |