Delivery Lab
카페 배달을 잠금화면까지 연결하며, 질문과 실험을 하나씩 쌓아가는 React Native 개발 기록입니다.
목차
잠금화면에 내용이 나오는 것까지 확인하고, 이번에는 앱을 홈 화면으로 내렸다. 다이나믹 아일랜드에도 남은 시간이 보였다. 숫자도 줄어들었다.
그런데 너무 길었다.

처음 확인한 것은 ‘시간이 표시되는가’였다. 홈 화면에서 어느 정도 공간을 쓰는지는 그다음에야 봤다. 기능 확인을 통과한 화면이 보기에도 괜찮은 화면은 아니었다.
이번 글은 다이나믹 아일랜드의 폭을 줄이고, 짧은 상태 문구를 추가하고, 주문 시작과 표시 시작을 연결한 기록이다. 120초 자동 시연을 사용했다. 글을 준비하면서 당시 캡처와 검증 기록, 현재 코드를 대조했다.
작은 화면에 무엇을 남길까
다이나믹 아일랜드를 항상 같은 크기의 검은 상자라고 생각하면 설계가 어긋난다. 기본 compact는 카메라 양쪽의 leading과 trailing으로 나뉜다. 더 작은 minimal과 길게 눌러 펼치는 expanded도 있다. 어떤 형태가 나타나는지는 진행 중인 Activity와 시스템 상황에 영향을 받는다. Apple의 Live Activity 안내에서 이 구분을 확인했다.
배달 앱은 작은 공간에 어떤 정보를 남기는지도 찾아봤다. 참고할 만했던 것은 Swiggy 디자인 팀이 2023년에 공개한 Live Activity 제작 과정이었다. 그 사례의 compact는 왼쪽에 주문 상태 아이콘, 오른쪽에 ETA를 둔다. minimal에는 상태 아이콘만 남긴다.
여기서 참고한 것은 당시의 정보 배치다. 지금 출시된 앱의 화면과 완전히 같다는 의미도, 2023년 글의 기술 제약이 현재도 그대로라는 의미도 아니다.
Delivery Lab에도 먼저 아이콘과 타이머만 두는 구성을 적용했다. 상세 진행률이나 마지막 확인 시각은 잠금화면과 펼친 화면에 남겼다. 앱 안에 있는 정보를 작은 칸에 모두 다시 넣을 필요는 없었다.

타이머에는 사용할 폭이 필요했다
기존 compact의 SwiftUI 시스템 타이머에는 폭 제한이 없었다. 설치된 Expo Widgets의 compact 래퍼도 별도로 제한하지 않았다. 숫자 길이만큼 자리를 쓸 거라고 생각했지만, 실제 화면에서는 훨씬 넓게 늘어났다.
타이머의 숫자 크기를 줄이는 것보다 먼저, 타이머가 차지하는 영역을 제한했다. 현재 trailing에 적용한 modifier 중 핵심은 다음 세 가지다.
숫자의 폭을 일정하게 하고, 오른쪽에 정렬하고, 사용할 영역을 정했다. 44pt는 Apple이 요구하는 규격이 아니라 이 프로젝트의 120초 시연에 맞춰 선택한 값이다. 더 긴 시간 표기나 다른 기기까지 그대로 적용할 수 있다는 보장은 없다.
같은 시뮬레이터에서 저장한 원본 캡처로 전후 폭도 비교했다. 상단 센서 영역의 가장 긴 검은 가로 구간을 픽셀로 읽고 배율 3으로 나누어 추정했다. 이전 화면은 약 327pt, 아이콘과 타이머를 둔 중간안은 약 224pt였다. OS에서 직접 읽은 레이아웃 값이 아니라, 같은 기기의 캡처를 비교한 결과다.
이때까지만 보면 아이콘과 시간으로 끝내도 될 것 같았다. 하지만 다음 요구가 생겼다. ‘픽업 중인지, 배달 중인지도 글자로 보였으면 좋겠다.’
상태 한 줄이 아이콘 자리를 차지했다
아이콘은 작게 넣기 좋다. 대신 처음 보는 사람이 커피잔 하나만 보고 조리 중인지 배달 중인지 구별하기는 어렵다. Delivery Lab에서는 상태를 바로 읽을 수 있는 쪽을 선택했다.
compact leading의 아이콘을 짧은 상태 문구로 바꿨다. ‘조리 중’, ‘픽업 대기’, ‘픽업 완료’, ‘배달 중’처럼 기존 서버 응답에서 얻은 고객용 요약을 사용한다. 매장과 라이더의 내부 상태를 이곳에 전부 나열하지는 않았다.
최종 구성은 왼쪽 상태 영역 56×26pt, 오른쪽 타이머 영역 44pt다. 상태는 한 줄로 제한했고, 정보가 오래되면 작은 ‘이전 상태’ 안내를 덧붙였다. 그만큼 아이콘만 있을 때보다 공간이 필요하므로, 앞에서 측정한 224pt를 최종 상태 문구 버전의 폭으로 쓰지는 않는다.

실제 시뮬레이터에서는 앱이 ‘조리 중’을 받은 뒤 홈 화면으로 나가 조리 중 1:27을 확인했다. 앱에 돌아와 배달 단계 응답을 받은 다음에는 홈 화면에 ‘배달 중’과 줄어드는 시간이 함께 보였다. 두 화면에서 글자와 숫자가 잘리지 않았고 시각, Wi-Fi, 배터리 표시도 보였다.
픽업 문구와 오래된 정보 분기는 직렬화된 레이아웃을 실행하는 테스트로 확인했다. 당시 compact의 픽업 구간이나 ‘이전 상태’ 두 줄을 실제 화면에서 포착한 것은 아니다. 펼친 화면, VoiceOver 낭독, 다른 기기 크기도 이 관찰에 포함하지 않았다.
시간이 흐른다고 새 소식이 온 것은 아니다
홈 화면에서 시간이 줄어드는 모습은 꽤 그럴듯했다. 저장한 캡처에는 1:44와 1:28이 남아 있다. 하지만 이 숫자가 무엇을 증명하는지는 구분해야 했다.
표시에는 JavaScript의 반복 타이머 대신 SwiftUI 시스템 카운트다운을 사용했다. 서버에서 받은 예상 시각을 기기 시각 기준으로 변환해 넘기면 시스템이 남은 시간을 그린다. Apple의 동적 날짜 표시 문서도 위젯 확장이 실행 중이지 않을 때 갱신되는 시간 표현을 설명한다.
마지막 예상 시각으로 계산한 숫자가 바뀌는 것과, 서버의 새 상태가 도착하는 것은 별개다. 당시 앱은 전경에서만 주문을 조회했다. 홈 화면에서 16초가 줄었다고 해서 그동안 라이더의 새 위치를 받은 것은 아니다.
초기 구현은 정보가 오래되면 타이머를 숨겼다. 이후 마지막 예상 시간이라도 계속 보고 싶다는 요구에 맞춰, 타이머를 유지하면서 오래된 정보라는 안내를 함께 표시하도록 바꿨다. 상세 화면에는 ‘마지막 예상 기준’, ‘업데이트를 기다리고 있어요’, 마지막 확인 시각을 남겼다.
잠금화면에서는 마지막 예상 안내와 0:00이 함께 나타나는 것도 관찰했다. 이때 마지막으로 확인한 ‘배달 중’은 그대로였다. 3편에서 정한 것처럼, 시간이 다 됐다는 이유만으로 전달 완료를 만들어내지 않았다. staleDate를 넣었다고 정확히 그 시각에 오래됨 안내가 다시 그려지는 것도 아니어서, 마지막 확인 시각을 함께 보여주는 의미가 있었다.
minimal에는 여전히 아이콘을 둔다. 다른 Activity와 경쟁하면 시스템이 이 작은 표현을 선택할 수 있다. 그래서 ‘추적 중에는 항상 시간이 보인다’고 소개할 수는 없다. 시간을 넣을 공간이 있는 표현에서 어떻게 읽히는지 확인한 것이다.
시작 버튼은 하나인데 성공은 두 가지였다
화면을 확인하는 동안 주문을 만든 뒤 다시 ‘잠금화면에 표시’를 누르는 것도 번거로웠다. ‘새 배달 시작하기’에 두 동작을 연결했다. 주문 생성에 성공하면 그 주문의 Activity를 한 번 시작한다. 홈으로 나가거나 화면을 잠그는 동작은 사용자가 한다.
버튼은 하나가 됐지만 주문 생성 성공과 네이티브 표시 성공은 분리했다. 표시가 실패했다고 이미 만들어진 주문을 없애거나 주문 생성 실패로 안내하면 안 된다. 주문 화면을 유지하고, 표시를 다시 시도할 수 있게 했다.
또 조회할 때마다 Activity를 시작하면 사용자가 지운 표시가 되살아날 수 있다. 시작 의도는 주문을 생성한 동작에서 한 번만 얻는다. 저장된 주문을 복원하거나 링크로 주문을 열었다는 이유만으로 새 Activity를 만들지는 않는다. 네이티브 준비가 늦으면 시작 의도를 유지하다가 준비 뒤 적용한다.
이 과정에는 조금 예상 밖의 회귀도 있었다. 네이티브 준비를 기다리던 중 취소 요청이 실패하면, 성공한 주문 생성 결과까지 지워져 자동 시작 의도가 사라졌다. 생성 오류만 초기화하도록 바꾸고, 준비 지연과 취소 실패를 조합한 통합 테스트를 추가했다. 수정 전에는 시작 0회로 실패했고, 수정 뒤에는 1회로 통과했다. 실제 시뮬레이터에 통신 장애를 만든 실험은 아니다.
최종 시뮬레이터에서는 완료 뒤 새 배달, 취소 뒤 새 배달 모두 버튼 한 번으로 표시가 시작되는 것을 확인했다. 상태 문구 수정 당시에는 모바일 테스트 78개와 타입 검사, iOS 빌드가 통과했다. 여기서 적은 수치는 해당 수정 당시의 검증 기록이며, 글을 쓰면서 새로 실행한 결과는 아니다.
작은 공간을 정리하다 보니 어느 정보가 필요한지뿐 아니라 그 정보가 언제 확인된 것인지도 함께 결정해야 했다. 상태와 시간을 읽기 좋게 배치해도, 홈으로 나간 뒤 상태가 계속 조리 중에 머무르면 배달 추적이라고 느끼기 어렵다.
6편에서는 앱이 백그라운드에 있을 때 상태를 갱신하고 배달이 끝나면 표시를 제거하는 과정을 다룬다. 최초 종료 처리부터 이후 추가한 시뮬레이터의 중간 상태 갱신까지 이어간다.
