Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ 스크롤 시 프로필 카드 10개씩 업데이트 기능 구현 #1

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions lib/features/profile/services/member_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ class MemberService {
final Dio _dio;
MemberService(this._dio);

Future<List<MemberModel>> getMembers(BuildContext context) async {
Future<List<MemberModel>> getMembers(BuildContext context, int page) async {
try {
final response = await _dio.get('${StringConstants.baseUrl}/home');
final response =
await _dio.get('${StringConstants.baseUrl}/home?page=$page');
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서버에서 page 별로 10개의 멤버 객체를 받을 수 있다고 해서

  1. 쿼리 문자열 파라미터로 page를 추가
  2. 동적으로 다음 page의 멤버 객체를 받아올 수 있도록 인자로 page 변수를 추가했습니다.

if (response.data['code'] == 200) {
final List<dynamic> result = response.data['data']['members'];
final members = result.map((e) => MemberModel.fromJson(e)).toList();
Expand Down
146 changes: 83 additions & 63 deletions lib/features/tutis/widgets/tuti_widgets/tuti_card_mobile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,79 +23,87 @@ class TuTiCardMobile extends ConsumerStatefulWidget {
}

class _TuTiCardMobileState extends ConsumerState<TuTiCardMobile> {
Future<List<MemberModel>> getMembersBuilder() async {
late ScrollController _scrollController;
int _page = 0;
List<MemberModel> _allMembers = [];

@override
void initState() {
super.initState();
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
_initializeMemberData();
}

Future<void> _initializeMemberData() async {
await getMembersBuilder(page: _page);
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. ScrollController
  • 화면의 끝에 닿을 시 이를 감지하는 객체하는 객체입니다.
  • State 객체가 만들어질 때 컨트롤러를 초기화하고 _scrollListener 라는 함수를 listen하도록 하였습니다.
  1. _initializeMemberData
  • iniState에서는 비동기 처리를 하지 않기를 권장하기에 직접적으로 비동기를 처리하지 않도록 함수 안에 넣어줬습니다!

Future<List<MemberModel>> getMembersBuilder({required int page}) async {
final memberService = ref.read(memberServiceProvider);
final members = await memberService.getMembers(context);
return members;
print(page);
final members = await memberService.getMembers(context, page);

if (members.isNotEmpty) {
setState(() {
_allMembers.addAll(members);
});
}

return _allMembers;
}

void _getDetailProfile(int memberId) async {
if (context.mounted) {
context.pushNamed(
TuTiDetailScreen.routeName,
params: {'tab': 'tuti'},
queryParams: {
'memberId': memberId.toString(),
},
);
void _scrollListener() {
// 리스트의 맨 아래에 도달했을 때
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
//서버에서 받아온 멤버 객체가 10의 배수일 때만 서버의 다음 페이지에 대한 멤버 정보를 받아옴.
if (_allMembers.length % 10 == 0) {
_page++;
getMembersBuilder(page: _page);
}
Comment on lines +56 to +64

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

창환님 궁금한 점이 있어 질문 드립니다!

  1. 10의 배수일 때 다음 페이지에 대한 정보를 받아오신다고 하셨는데, 10의 배수가 아닌 경우(11, 12, 13 ... ) 의 경우에는 다음 페이지를 불러오지 않는 걸까요?

  2. 딱 10개인 경우 다음 페이지가 없을 거 같은데 이 경우에도 호출하는 걸까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

답변 드립니다!

  1. 맞습니다! 10의 배수가 아닌 경우에는 다음 페이지에 대한 요청을 하지 않도록 조건을 걸어놨어요.
  2. 10개인 경우에는 다음 페이지에 쿼리를 하되 멤버 객체가 없으면 똑같이 아래 로직을 따라가도록 설정하려고 했어요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서버 응답 필드 중 hasNext는 다음 페이지의 여부 값이 담겨있는데요!

혹시 사용하시지 않고 직접 개수를 체크하시는 이유가 있을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앗 그 여부를 놓쳤네요..! 피드백 감사합니다! 수정하겠습니당 👍

}
}

@override
Widget build(BuildContext context) {
return Expanded(
child: FutureBuilder<List<MemberModel>>(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FutureBuilder로 해당 기능을 구현하려고 할 시,
리스트의 끝에 도달할 때마다 전체 화면이 다시 그려지게 돼서

유저의 스크롤 포인트가 화면 제일 위로 초기화되는 문제가 있어 이를 해결하기 위해 ListView.builder 위젯을 사용했습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다만 비동기 객체라 1) 로딩 2) 에러 시 분기 처리를 해줘야할 것 같습니다..!

future: getMembersBuilder(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
return const Center(
child: Text('데이터를 불러오는데 실패했습니다.'),
);
}
final members = snapshot.data!;
return ListView.separated(
itemCount: members.length,
separatorBuilder: (context, index) {
return Gaps.h32;
},
itemBuilder: (context, index) {
final member = members[index];
return Padding(
padding: EdgeInsets.symmetric(horizontal: 10.sp),
child: Container(
constraints: BoxConstraints(
minHeight: 250.h,
maxHeight: 250.h,
),
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
side: const BorderSide(
width: 2,
color: ColorConstants.primaryColor,
),
borderRadius: BorderRadius.circular(45),
),
),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildLeftColumn(context, member),
Gaps.w24,
_buildRightColumn(context, member),
],
),
child: ListView.builder(
controller: _scrollController,
itemCount: _allMembers.length,
itemBuilder: (context, index) {
final member = _allMembers[index];
return Padding(
padding: EdgeInsets.symmetric(horizontal: 10.sp),
child: Container(
constraints: BoxConstraints(
minHeight: 250.h,
maxHeight: 250.h,
),
margin: EdgeInsets.symmetric(vertical: 10.sp),
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
side: const BorderSide(
width: 2,
color: ColorConstants.primaryColor,
),
);
},
);
}),
borderRadius: BorderRadius.circular(45),
),
),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildLeftColumn(context, member),
Gaps.w24,
_buildRightColumn(context, member),
],
),
),
);
},
),
);
}

Expand Down Expand Up @@ -157,6 +165,18 @@ class _TuTiCardMobileState extends ConsumerState<TuTiCardMobile> {
);
}

void _getDetailProfile(int memberId) async {
if (context.mounted) {
context.pushNamed(
TuTiDetailScreen.routeName,
params: {'tab': 'tuti'},
queryParams: {
'memberId': memberId.toString(),
},
);
}
}

Widget _buildSwitchRow(BuildContext context, MemberModel member) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
Expand Down