[개발글쓰기 7일차] 와이어 프레임 작성하기 - 3(Comment, .add, .PutIfAbsent)

이혁진·2024년 3월 24일
0

저번시간에는 와이어 프레임을 작성하기 전 구현 계획과 목표를 세웠다.
Head, Content, Comment 로 컴포넌트를 나눴고, 선택된 Task 를 어떻게 주입할지도 정하였었다.

구현 중 Head 와 Content 는 문제가 되지 않았다. 그냥 주입하고 정보를 출력하면 쉬웠다. 하지만 Comment 부분은 구현하기 여간 까다로웠다.

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 등등 필요한 코드들을 작성해줄래?

고려할 사항

  • 플랫 구조
    플랫이란 '평평한' 이라는 의미를 가지고 있다. 즉 백엔드에서 일렬로 데이터를 준다면 , 프론트에서 트리 구조로 해석하여 표현해야한다.
  • 댓글과 답글의 깊이 단계는 최대 2 이다.

일단 Entity 를 임의적으로 생성하고 Provider 로 전달하고자 했다.

Entity

위 고려 사항을 반영해서 엔티티를 작성했다.

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로 전달할 차례이다.

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

데이터 정렬

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;: 각 commentparentId를 확인합니다. 만약 parentIdnull이면, 해당 댓글이 최상위 댓글(즉, 부모 댓글)임을 나타내기 위해 댓글 자체의 ID를 parentId로 사용합니다.

  • groupedComments.putIfAbsent(parentId, () => []).add(comment);: 이 코드는 parentId를 키로 사용하여 groupedComments 맵에서 해당 키의 값을 찾습니다. 만약 해당 키(parentId)에 대한 값이 없으면, 빈 리스트 []를 생성하여 맵에 추가합니다. 그리고 나서, 현재 처리 중인 comment를 해당 키의 값(리스트)에 추가합니다.

코드의 목적:

이 코드의 주된 목적은 댓글과 답글을 부모-자식 관계로 구조화하는 것입니다. parentId를 기준으로 댓글을 그룹화함

예시:

예를 들어, 다음과 같은 댓글 구조가 있다고 가정해 보겠습니다.

  • 댓글 1 (ID: 1, parentId: null)
    • 답글 A (ID: 2, parentId: 1)
    • 답글 B (ID: 3, parentId: 1)
  • 댓글 2 (ID: 4, parentId: null)

위 코드를 실행한 결과, groupedComments 맵은 다음과 같은 구조를 가지게 됩니다:

  • 1: [댓글 1, 답글 A, 답글 B]
  • 4: [댓글 2]

이러한 방식으로 댓글과 답글을 관리함으로써, 각 댓글에 속한 답글을 쉽게 찾아 UI에 표시할 수 있게 됩니다.


궁금했던 점(.add, .putIfAbsent)

추가적으로 나는 groupedComments.putIfAbsent(parentId, () => []).add(comment); 이 부분의 .add(comment); 부분이 어떻게 작동하는지도 궁금하였다.

groupedComments 맵에 댓글 객체(CommentEntity)를 추가하는 과정입니다.

groupedComments.putIfAbsent(parentId, () => []).add(comment);의 작동 방식:

  1. putIfAbsent(key, () => defaultValue) 메서드: 이 메서드는 Map에서 사용됩니다. 만약 Map에 주어진 키(key, 여기서는 parentId)가 이미 존재한다면, 아무런 동작도 하지 않습니다. 키가 존재하지 않는 경우, 메서드에 전달된 람다 함수 () => defaultValue가 호출되어, Map에 새 키-값 쌍이 추가됩니다. 여기서 defaultValue는 빈 리스트 []입니다. 즉, parentId를 키로 하는 새로운 리스트가 Map에 추가됩니다(이 리스트는 특정 parentId를 가진 모든 댓글을 담기 위한 것입니다).

  2. .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 맵은 다음과 같은 구조를 가지게 됩니다:

  • 1: [댓글 A, 댓글 B]

이렇게 각 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 에 표시

이제 ui 와 provider 을 연결하고, 표시하면 된다.
다음 포스팅에서 알아보고자 하겠다.

profile
앱 개발과 AI, 서비스에 관심이 많은 학생입니다.

2개의 댓글

comment-user-thumbnail
2025년 4월 4일

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!

답글 달기
comment-user-thumbnail
2025년 4월 18일

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.

답글 달기