저번시간에는 와이어 프레임을 작성하기 전 구현 계획과 목표를 세웠다.
Head, Content, Comment 로 컴포넌트를 나눴고, 선택된 Task 를 어떻게 주입할지도 정하였었다.
구현 중 Head 와 Content 는 문제가 되지 않았다. 그냥 주입하고 정보를 출력하면 쉬웠다. 하지만 Comment 부분은 구현하기 여간 까다로웠다.
gpt 에게 필요한 정보를 물어봤다.
이00
댓글 어쩌구 저쩌구 댓글 어쩌구 저쩌구 댓글 어쩌구 저쩌구
2023.01.16. 12:55
답글
⏎
이00
댓글 어쩌구 저쩌구 댓글 어쩌구 저쩌구 댓글 어쩌구 저쩌구
2023.01.16. 12:55
이러한 방식의 댓글 UI를 작성하려 하고 있어
댓글과 답글의 깊이 단계는 최대 2로 한정되어 있어
Flutter riverpod 을 사용하려하며 서버의 데이터를 받고 상태관리를 하려해
다음은 서버 응답 데이터의 예시야
[
{
"id": 1,
"authorName": "이00",
"content": "댓글 어쩌구 저쩌구",
"createdAt": "2023-01-16T12:55:00",
"parentId": null
},
{
"id": 2,
"authorName": "이00",
"content": "답글 어쩌구 저쩌구",
"createdAt": "2023-01-16T13:00:00",
"parentId": 1
},
// 추가 댓글 및 답글 데이터...
]
플렛 구조로 되어 있고 클라이언트에서 재구성하는 과정이 필요해
이 모든 것을 고려해서 ui,provider, data model, entity 등등 필요한 코드들을 작성해줄래?
일단 Entity 를 임의적으로 생성하고 Provider 로 전달하고자 했다.
위 고려 사항을 반영해서 엔티티를 작성했다.
class CommentEntity {
CommentEntity({
required this.id,
required this.authorName,
required this.content,
required this.createdAt,
this.parentId,
});
final int id;
final String authorName;
final String content;
final DateTime createdAt;
final int? parentId;
}
이제 임시 엔티티 인스턴스를 만들고 provider로 전달할 차례이다.
@riverpod
class Comment extends _$Comment {
@override
List<CommentEntity> build() {
return [
CommentEntity(
id: 1,
authorName: '홍길동',
content: '첫 번째 댓글입니다.',
createdAt: DateTime.now(),
parentId: null,
),
CommentEntity(
id: 2,
authorName: '곽두팔',
content: '두 번째 댓글입니다.',
createdAt: DateTime.now(),
parentId: 1,
),
CommentEntity(
id: 3,
authorName: '김철수',
content: '세 번째 댓글입니다.',
createdAt: DateTime.now(),
parentId: null,
),
CommentEntity(
id: 4,
authorName: '임꺽정',
content: '네 번째 댓글입니다.',
createdAt: DateTime.now(),
parentId: 1,
)
// 추가 댓글 및 답글 데이터...
];
}
}
provider 에는 정말 아무런 기능도 없고 단순히 만들어진 임시 엔티티를 전달할 뿐이다.
ui 단에서는 받은 데이터들을 부모 - 자식 간의 트리 구조로 변환하는 과정을 만들었다.
먼저 참고한 코드이다.
class CommentsWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final comments = ref.watch(commentProvider).map((model) => model.toEntity()).toList();
// Group comments by parentId, with null parentId indicating a top-level comment
final Map<int?, List<CommentEntity>> groupedComments = {};
for (var comment in comments) {
final parentId = comment.parentId ?? comment.id;
groupedComments.putIfAbsent(parentId, () => []).add(comment);
}
return ListView.builder(
itemCount: groupedComments.keys.length,
itemBuilder: (context, index) {
final key = groupedComments.keys.elementAt(index);
final List<CommentEntity> group = groupedComments[key]!;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(group[0].authorName + ': ' + group[0].content), // Top-level comment
for (var reply in group.skip(1)) // Replies
Padding(
padding: const EdgeInsets.only(left: 16.0),
child: Text(reply.authorName + ': ' + reply.content),
),
],
);
},
);
}
}
데이터를 정렬하는 곳을 살펴보자면,
final comments = ref.watch(commentProvider).map((model) => model.toEntity()).toList();
// Group comments by parentId, with null parentId indicating a top-level comment
final Map<int?, List<CommentEntity>> groupedComments = {};
for (var comment in comments) {
final parentId = comment.parentId ?? comment.id;
groupedComments.putIfAbsent(parentId, () => []).add(comment);
이 코드는 CommentEntity
객체의 리스트인 comments
를 반복 처리하여, 각 댓글을 부모 ID(parentId
)를 기준으로 그룹화하는 과정을 설명한다.
final Map<int?, List<CommentEntity>> groupedComments = {};
: 여기서 groupedComments
는 댓글을 그룹화하기 위한 Map
을 선언합니다. 이 Map
의 키는 int?
타입으로, 댓글의 parentId
를 나타냅니다. 값은 List<CommentEntity>
타입으로, 동일한 parentId
를 가지는 CommentEntity
객체들의 리스트입니다.
for (var comment in comments) { ... }
: 입력으로 받은 comments
리스트를 순회하며, 각 comment
에 대해 처리를 수행합니다.
final parentId = comment.parentId ?? comment.id;
: 각 comment
의 parentId
를 확인합니다. 만약 parentId
가 null
이면, 해당 댓글이 최상위 댓글(즉, 부모 댓글)임을 나타내기 위해 댓글 자체의 ID를 parentId
로 사용합니다.
groupedComments.putIfAbsent(parentId, () => []).add(comment);
: 이 코드는 parentId
를 키로 사용하여 groupedComments
맵에서 해당 키의 값을 찾습니다. 만약 해당 키(parentId
)에 대한 값이 없으면, 빈 리스트 []
를 생성하여 맵에 추가합니다. 그리고 나서, 현재 처리 중인 comment
를 해당 키의 값(리스트)에 추가합니다.
이 코드의 주된 목적은 댓글과 답글을 부모-자식 관계로 구조화하는 것입니다. parentId
를 기준으로 댓글을 그룹화함
예를 들어, 다음과 같은 댓글 구조가 있다고 가정해 보겠습니다.
위 코드를 실행한 결과, groupedComments
맵은 다음과 같은 구조를 가지게 됩니다:
이러한 방식으로 댓글과 답글을 관리함으로써, 각 댓글에 속한 답글을 쉽게 찾아 UI에 표시할 수 있게 됩니다.
.add
, .putIfAbsent)추가적으로 나는 groupedComments.putIfAbsent(parentId, () => []).add(comment);
이 부분의 .add(comment);
부분이 어떻게 작동하는지도 궁금하였다.
groupedComments
맵에 댓글 객체(CommentEntity
)를 추가하는 과정입니다.
groupedComments.putIfAbsent(parentId, () => []).add(comment);
의 작동 방식:putIfAbsent(key, () => defaultValue)
메서드: 이 메서드는 Map
에서 사용됩니다. 만약 Map
에 주어진 키(key
, 여기서는 parentId
)가 이미 존재한다면, 아무런 동작도 하지 않습니다. 키가 존재하지 않는 경우, 메서드에 전달된 람다 함수 () => defaultValue
가 호출되어, Map
에 새 키-값 쌍이 추가됩니다. 여기서 defaultValue
는 빈 리스트 []
입니다. 즉, parentId
를 키로 하는 새로운 리스트가 Map
에 추가됩니다(이 리스트는 특정 parentId
를 가진 모든 댓글을 담기 위한 것입니다).
.add(comment)
: putIfAbsent
메서드가 실행된 후, groupedComments
맵의 해당 parentId
키에 대응하는 리스트(이미 존재하거나 새로 생성됨)가 반환됩니다. 그리고 나서 .add(comment)
코드가 이 리스트에 현재 처리 중인 댓글 객체(comment
)를 추가합니다. 즉, 특정 parentId
를 가진 댓글 그룹에 새 댓글을 추가하는 것입니다.
이 코드의 핵심 목적은 모든 댓글을 부모 댓글의 ID(parentId
)를 기준으로 그룹화하여 관리하는 것입니다. 최상위 댓글은 자신의 ID를 parentId
로 사용하고, 답글은 원본 댓글의 ID를 parentId
로 사용합니다. 이 방식을 통해, 댓글과 관련 답글을 함께 관리하고, UI에서 구조적으로 표시하기 용이하게 만듭니다.
예를 들어, 댓글 1에 두 개의 답글(A, B)이 있고, groupedComments
에는 아직 parentId
1에 대한 항목이 없는 경우, 처음 comment A
가 처리될 때, parentId
1을 키로 하는 빈 리스트가 생성되고 comment A
가 이 리스트에 추가됩니다. 이후 comment B
가 처리될 때는 이미 parentId
1을 키로 하는 리스트가 존재하므로, comment B
는 바로 이 리스트에 추가됩니다.
결과적으로, groupedComments
맵은 다음과 같은 구조를 가지게 됩니다:
이렇게 각 parentId
에 해당하는 댓글 리스트를 관리함으로써, 댓글과 답글의 관계를 명확하게 구조화하여 관리할 수 있습니다.
정리하자면 코드를 정렬하는 법은 다음과 같다.
1. 임의의 List<Map<int, List<CommentEntity>> 을 만든다.
2. comment 의 parentID == null
이면 키 값이 자신의 id 인 map
을 생성한다. 이 때 value 는 빈 리스트를 반환한다.
3. .add(commnet)
를 통해 (putIfAbsent(key, () => defaultValue)
함수는 this.List를 반환한다.) value 값의 comment 를 추가한다.
결과적으로 이렇게 나타난다.
댓글 1 (ID: 1, parentId: null)
답글 A (ID: 2, parentId: 1)
답글 B (ID: 3, parentId: 1)
댓글 2 (ID: 4, parentId: null)
1: [댓글 1, 답글 A, 답글 B]
4: [댓글 2]
이제 ui 와 provider 을 연결하고, 표시하면 된다.
다음 포스팅에서 알아보고자 하겠다.
Experiencing the Delhi Nights? Have Exclusive Airhostess Delhi Escorts Ignite Your Desires! Ready to break out of the monotonous routine? Experience a world of endless pleasure with Delhi Escorts. Envision a first-class, sensual experience delivered to your doorstep discreetly. Our exclusive high-rated Escorts Delhi are not merely pretty faces; they are seductive companions eager to satisfy your most intimate fantasies. Book Tonight and feel the difference. No matter if you're looking for a sultry night in or a wildest night out, our Delhi Escort Service is your doorway to experiencing unforgettably memorable moments. Don't do less; deserve the best and fabulous.
Tired of the Same Old Routine? Dhaula Kuan Escorts are your escape hatch! Imagine unwinding tonight with an exclusive, high-rated companion. We offer top-level Escorts in Dhaula Kuan, from seductive Russian Escorts Girls to exciting collage Escorts Girls. Craving adult adventures? Our Dhaula Kuan Escort Service brings the wildest fantasies 24/7 in your doorstep. Book tonight for an excellent experience, incall or outcall. Discreet, pocket-friendly, and designed for your ultimate pleasure. Don't just dream about erotic excitement – live it with Escorts Dhaula Kuan!