Delivery Lab
카페 배달을 잠금화면까지 연결하며, 질문과 실험을 하나씩 쌓아가는 React Native 개발 기록입니다.
목차
지난 글까지는 iPhone 시뮬레이터에서 배달 상태를 보여줬다. 앱을 잠그고 기다려도 조리 중에서 배달 중으로 바뀌고, 전달이 끝나면 표시가 사라졌다. 여기까지 만들고 나니 Android 폰에서도 한번 보고 싶어졌다.
주문을 받고, 커피를 만들고, 라이더가 이동하는 시나리오는 이미 있었다. Android에서도 같은 120초 배달을 시작하면 된다. RN 코드를 어디까지 그대로 쓸 수 있을지 궁금했다.
연결한 폰은 Android 15였다
Android에는 Live Updates와 ProgressStyle이 있었다. 배달처럼 진행 중인 작업에 맞는 표시였다. 다만 ProgressStyle은 Android 16에서 도입됐고, 테스트하려고 연결한 개인 폰은 Android 15였다.
최신 기능이 있다는 설명을 읽고 바로 폰에서 찾아보면 만날 수 없는 조합이었다. 기기를 바꾸는 대신 두 환경에서 확인하기로 했다.
| 테스트 환경 | 이번에 확인할 표시 |
|---|---|
| SM-F711N · Android 15 · One UI 7 | 상태와 진행률이 있는 일반 진행 알림 |
| 전용 에뮬레이터 · Android 16 / API 36.1 | ProgressStyle과 Live Updates 상태 표시줄 칩 |
같은 배달 시나리오로 각각 주문을 만들었다. 두 기기가 하나의 주문을 동시에 추적하는 실험은 하지 않았다.

기존 ActivityPort에 Android 구현을 연결했다
기존 코드에는 표시를 시작하고 갱신하고 끝내는 ActivityPort가 있었다. RN 주문 화면은 이 인터페이스를 호출한다. 실제 표시를 연결하는 구현은 그 뒤에 둔다.
Android 쪽은 Expo Modules로 로컬 모듈 DeliveryProgress를 만들고, Kotlin에서 알림을 게시하고 지우도록 했다. Android용 adapter를 추가해 기존 ActivityPort의 호출을 이 모듈로 넘겼다.
| 그대로 사용한 부분 | Android에 추가한 부분 |
|---|---|
| 서버 시간에 따른 배달 상태·가상 경로 | Android 진행 알림·ProgressStyle |
| 주문 조회·생성·취소와 캐시 판정 | 알림 권한 요청과 앱 알림 설정 이동 |
| 주문 생성 후 한 번 표시하는 흐름 | 알림 인스턴스 저장·사용자 삭제 감지 |
| 조리 중·픽업 대기·배달 중 등의 문구 | 마지막 확인 시각과 예상 시각 배치 |
단계 문구를 Kotlin에 다시 적지는 않았다. 공유 도메인에서 사용하는 라벨을 모듈에 전달했다. 조리 중이라는 상태의 이름을 바꿀 때 두 언어의 목록을 함께 찾아다니고 싶지는 않았다.
RN과 네이티브가 만나는 부분에서도 입력을 확인했다. 모듈이 돌려준 권한 상태와 인스턴스 ID는 unknown으로 받아 Zod로 검사한다. Android 인스턴스에는 iOS push token 메서드를 넣지 않아 기존 iOS 원격 등록이 실행되지 않도록 했다.
주문 화면은 재사용했다. 플랫폼별 권한과 표시 수명은 직접 다뤄야 했다.
알림 권한을 거절해도 주문은 남아야 했다
새 배달 시작 버튼에서 주문 생성과 알림 시작을 이어 붙였다. 이때 알림 권한을 거절했다고 주문까지 실패로 바뀌면 이상하다. 서버에는 이미 주문이 만들어져 있기 때문이다.
개인 폰에서 이 앱의 알림 권한을 ADB로 차단해 복구 흐름을 확인했다. 주문 화면과 진행 정보는 남기고, 알림을 다시 시도하거나 해당 앱의 알림 설정으로 이동할 수 있게 했다. 설정 화면에서 허용한 뒤 새 배달을 시작하자 알림이 생성됐다. 테스트 뒤에는 임시 차단 플래그도 해제했다.
첫 권한 요청 창을 확인한 뒤에는 차단된 사용자가 돌아올 길도 확인했다.
요청했다고 칩이 뜨는 건 아니었다
API 36 이상에서는 NotificationCompat.ProgressStyle을 사용하고 setRequestPromotedOngoing(true)로 Live Updates 승격을 요청했다. 설치된 AndroidX Core 1.17.0의 Compat API를 사용해 compileSdk 36에서 빌드했다. 플랫폼 Builder의 같은 이름 메서드는 API 36.1에 추가됐으므로 예제의 import까지 구분해서 봐야 했다.
공식 안내에 나온 승격 조건을 맞춘 뒤에도 실제 표시를 확인했다. 에뮬레이터의 알림 덤프에 PROMOTED_ONGOING 플래그가 남았고, 홈 화면의 시계 옆에는 작은 트럭과 11:47이 나타났다.

알림창을 펼치면 배달 중이라는 문구와 마지막 확인 시각, 트럭이 놓인 진행 막대가 보였다. 짧은 칩에는 예상 시각을 두고 자세한 상태는 알림에서 읽도록 했다. iOS 다이나믹 아일랜드의 배치를 그대로 옮기지는 않았다.

이 주문은 11:47:48에 서버의 전달 완료를 처리한 뒤 칩과 알림이 사라졌다. 개인 Android 15 폰에서는 일반 진행 알림으로 홈·잠금 중 갱신과 완료·취소 후 제거를 확인했다. 여기서 확인한 결과는 이 두 환경의 결과다. 다른 제조사나 사용자 설정에서도 같은 자리에 같은 모양으로 나온다고 말할 수는 없다.
잠금화면에서는 어디로 갔을까
에뮬레이터를 잠그니 처음에는 큰 배달 카드가 보이지 않았다. 알림이 사라졌나 싶었는데, 표시 목록에는 여전히 남아 있었다.
잠금화면을 다시 보니 트럭이 다른 시스템 알림 아이콘과 함께 작은 묶음 안에 들어 있었다. 이 부분을 누르자 잠금을 해제하지 않은 채 알림창에 배달 카드가 펼쳐졌다. Pixel 잠금화면 도움말의 Compact View와 Full list 구분이 이 관찰을 이해하는 데 도움이 됐다.

확인 과정에서 전용 에뮬레이터의 잠금화면을 활성화하고 조용한 알림 표시 설정도 바꿨다. 설정 전후를 같은 주문으로 비교하지 못해 변경 효과까지 판단할 수는 없었다. 개인 폰은 원래 잠금화면 설정을 유지했고, 아이콘과 잠긴 상태의 알림창을 확인했다. 삼성 Now Bar를 구현한 결과는 아니다.
앱을 나간 뒤에는 누가 갱신했나
이 시연에서 앱 밖의 갱신은 Mac의 개발용 worker가 도왔다. 새 알림 인스턴스가 생기면 debug 전용 receiver를 깨우고, 진행 중에는 5초 간격으로 다시 신호를 보낸다. Android 네이티브 코드는 저장된 현재 주문을 한 번 조회해 알림을 갱신한다. 완료·취소를 받으면 지우고, 사용자가 알림을 지워도 worker의 전송이 멈춘다.
USB·ADB 연결을 사용한 개발용 경로다. FCM으로 푸시를 전달한 실험은 아니다. JS 타이머가 백그라운드에서 계속 실행된다고 가정하지 않고 네이티브 갱신을 확인하려고 만든 통로였다. Doze 중 수신이나 사용자 강제 종료 뒤 복구는 아직 확인하지 않았다.
RN의 주문 로직을 유지한 채 Android 권한과 알림을 연결했다. 이제 표시 안의 숫자가 마음에 걸렸다. iOS에서는 줄어드는 남은 시간을 보여줬는데, Android 알림에도 같은 방식을 넣어두면 연결이 끊겼을 때 어떻게 읽힐까?
8편에서는 넣어두었던 카운트다운을 빼고, 마지막 확인 시각과 도착 예상 시각을 남긴 과정을 다룬다.
