플러터 과제

이담현·2026년 5월 28일
  1. 오늘의 목표
    일정 카드를 옆으로 밀면 삭제되는 기능을 Dismissible로 구현하고, TodayBanner의 일정 개수를 StreamBuilder로 연결해서 실시간으로 자동 갱신되도록 만든다.

  2. 핵심 개념
    Dismissible: Flutter에서 위젯을 손가락으로 밀어서(스와이프) 삭제할 때 사용하는 위젯이다. 리스트 항목을 직관적으로 제거하는 UX를 만들 때 필수적이다.

Key: Flutter가 여러 위젯 중 특정 위젯을 정확히 식별하고 구분하기 위해 사용하는 고유 값이다. 동적인 리스트에서 어떤 항목이 삭제되었는지 Flutter가 혼동하지 않도록 ObjectKey(schedule.id) 같은 고유값을 지정해야 한다.

Future vs Stream (데이터 다루기)

Future (한 번만 가져오는 데이터): getSchedules(selectedDate)처럼 한 번 요청하고 결과를 받으면 연결이 끝난다.

Stream (계속 지켜보는 데이터): 데이터가 일회성으로 끝나지 않고 흐르는 통로다. DB에 변화가 생길 때마다 자동으로 새 데이터를 앱에 흘려보내 준다.

State vs Cache (상태와 캐시)

State (상태): 화면에 직접적인 영향을 주는 데이터다. 값이 바뀌면 Flutter가 화면을 다시 그린다.

Cache (캐시): 데이터베이스나 서버에서 가져온 데이터를 메모리에 임시로 기억해두는 공간이다. 매번 새로 요청하지 않고 저장된 값을 빠르게 꺼내 쓸 수 있다.

  1. 코드 예시
    추가된 부분 1 — TodayBanner StreamBuilder 연동
    Dart
    StreamBuilder<List>(
    stream: GetIt.I().watchSchedules(selectedDate),
    builder: (context, snapshot) {
    return TodayBanner(
    selectedDate: selectedDate,
    count: snapshot.data?.length ?? 0,
    );
    },
    ),
    추가된 부분 2 — Dismissible 슬라이드 삭제
    Dart
    return Dismissible(
    key: ObjectKey(schedule.id),
    direction: DismissDirection.startToEnd,
    onDismissed: (DismissDirection direction) {
    GetIt.I().removeSchedule(schedule.id);
    },
    child: Padding(
    padding: const EdgeInsets.only(bottom: 8, left: 8, right: 8),
    child: ScheduleCard(
    startTime: schedule.startTime,
    endTime: schedule.endTime,
    content: schedule.content,
    ),
    ),
    );
  2. 코드 설명
    TodayBanner와 StreamBuilder 연동
    stream: GetIt.I().watchSchedules(selectedDate)

선택된 날짜의 일정 목록을 실시간으로 관찰하는 데이터 통로(Stream)를 엽니다. 로컬 DB의 일정 데이터가 변경될 때마다 StreamBuilder가 이를 감지하여 화면을 자동으로 다시 그립니다.

count: snapshot.data?.length ?? 0

?. (Null-safe 접근 연산자): 데이터가 아직 로드되지 않아 snapshot.data가 null인 상태여도 에러를 내지 않고 안전하게 접근합니다.

?? (Null 병합 연산자): 왼쪽 값이 null일 경우 우측의 기본값(0)을 사용하여, 데이터 공백기에도 앱이 튕기지 않도록 안전장치를 치는 패턴입니다.

Dismissible 슬라이드 삭제
key: ObjectKey(schedule.id)

리스트 내에서 각 일정 카드가 가진 고유한 식별자(ID)를 Flutter에게 알려줍니다. 위젯이 화면에서 사라지거나 재배치될 때 다른 위젯과 뼈대가 섞이지 않도록 보장하는 필수 속성입니다.

direction: DismissDirection.startToEnd

스와이프가 유효하게 작동할 방향을 왼쪽에서 오른쪽으로 제한합니다. 사용자가 반대 방향으로 밀 때는 삭제 동작이 트리거되지 않습니다.

onDismissed: (DismissDirection direction) { ... }

사용자가 카드를 끝까지 밀어 화면에서 완전히 사라진 직후 실행되는 콜백 함수입니다. 이 시점에 실제 데이터베이스(DB) 메서드인 removeSchedule을 호출하여 영구적으로 데이터를 삭제합니다.

  1. 실행 결과
    일정 카드를 왼쪽에서 오른쪽으로 밀면 카드가 부드럽게 사라지며 DB에서도 해당 일정이 정상적으로 삭제되었다.

일정이 삭제되자마자 StreamBuilder가 이를 감지하여 TodayBanner의 일정 개수가 실시간으로 줄어들었다.

달력에서 날짜를 변경해도 해당 날짜의 실시간 일정 개수가 배너에 즉각 반영되었다.

  1. 내가 바꿔본 점
    direction을 DismissDirection.endToStart로 바꿨더니, 오른쪽에서 왼쪽으로 밀었을 때만 삭제가 동작했다.

direction을 DismissDirection.horizontal로 바꿨더니 좌우 어느 방향으로 밀어도 삭제가 가능해졌다.

count: 0으로 숫자를 고정했을 때와 달리 StreamBuilder 스냅샷 연동 방식을 쓰니 일정을 넣고 뺄 때 배너 숫자가 알아서 척척 바뀌는 차이를 확인했다.

  1. 오늘 알게 된 점
    Dismissible은 key, direction, onDismissed라는 세 가지 핵심 속성만 잘 쥐어주면 까다로운 스와이프 삭제 기능을 생각보다 아주 쉽게 구현할 수 있다는 것을 배웠다.

?.와 ??를 조합하는 널 가드(Null Guard) 패턴이 실제 유저 피드백 데이터를 안전하게 가공할 때 얼마나 유용한지 체감했다.

데이터의 성격에 따라 한 번만 부르는 Future, 흐름을 유지하는 Stream으로 나누어 설계하는 명확한 기준이 생겼다.

  1. 아직 헷갈리는 점
    cache(캐시)를 임시로 담아 놓는 과정이 헷갈린다.
    만약 cache에 저장된 데이터가 최신 데이터와 다르다면?
profile
최고 최선의 코드를 생성하는 개발자

0개의 댓글