Corca Medium 아카이브
Live Activity, 더 깊게 사용해보기: 실시간 일정 기능 개발기
안녕하세요! Trace 팀에서 iOS Product Engineer로 일하고 있는 김성훈입니다.
요즘 대부분의 앱은 사용자들의 주의를 얻기 위해 푸시 알림을 쏟아냅니다.
하지만 우리는 모두 알고 있습니다. 알림은 쉽게 무시되고, 스크롤에 묻히고, 필요한 순간에는 제 역할을 하 지 못한다는 걸요.
반대로, 쿠팡이츠나 배달의민족처럼 잠금화면에서 배달 상태가 실시간으로 변경되는 Live Activity 경험은 사용자의 주의를 자연스럽게 사로잡습니다.
앱을 열지 않아도 지금 어디쯤 와 있는지, 얼마나 남았는지를 바로 확인할 수 있고, 이 흐름이 사용자 경험을 한층 더 매끄럽게 만들어줍니다.
실제로 Apple도 Live Activity를 일정·운동·배달·이동·타이머 등 ‘지속적으로 변화하는 정보’를 제공하는 데 최적화된 기능이라고 소개합니다.
Trace 디스코드 커뮤니티에서도 비슷한 유저의 요청이 있었습니다.
“앱을 굳이 열지 않아도 오늘 뭐 해야 하는지 한눈에 보고 싶어요.”
“잠금화면에서 지금 진행 중인 일정이 보이면 좋겠어요.”
그래서 Trace는 이 요구를 Live Activity로 어떻게 풀어낼 수 있을지 본격적으로 실험을 시작했습니다.
이 글에서는 Trace가 Live Activity를 활용해 실시간 일정 경험을 구현하는 과정에서 어떤 문제를 마주했고, 그 문제들을 어떻게 해결해 나갔는지 단계별로 정리해보려고 합니다.
APNs
iOS에서 모든 푸시는 APNs(Apple Push Notification service)를 통해 전달됩니다. APNs가 보내는 푸시 타입 은 크게 세 가지로 나눌 수 있습니다.
일반 알림(Alert Push)
사용자에게 보이는 배너/소리/진동 알림
Silent Push(백그라운드 푸시)
화면에는 나타나지 않고, 앱을 잠깐 깨워 백그라운드 작업을 실행하는 용도 ActivityKit Push(Live Activity 업데이트 푸시) Live Activity의 상태를 직접 갱신하는 데 사용되는 전용 타입 처음 Live Activity를 붙이기 시작했을 때는 ActivityKit Push가 어떤 구조로 동작하는지, 그리고 어떤 데이터 구조를 요구하는지가 명확히 잡혀 있지 않은 상태였습니다.
그래서 자연스럽게, 그동안 많이 사용해왔고 백그라운드 업데이트에도 익숙한 Silent Push부터 시도하게 됐습니다.
“앱을 조용히 깨워서 Live Activity만 업데이트하면 되지 않을까?”
가장 쉽게 떠올릴 수 있는 접근이었습니다.
하지만 여러 번 시도해본 끝에 중요한 사실을 하나 깨닫게 되었습니다.
Live Activity는 단순히 “업데이트 요청”을 보내는 구조가 아니라, 그 시점에 화면에 표시해야 할 데이터 전체 (contentState)를 푸시에 실어 보내야 합니다.
즉 서버가 “현재 어떤 일정이 진행 중인지, 얼마나 남았는지”를 직접 계산해서 보내야 하고, Silent Push 기반 구조는 이 요구를 안정적으로 충족시키기 어려웠습니다.
Live Activity 자동 업데이트
핵심 아이디어는 단순했습니다.
“앱을 깨우지 않고, 서버가 Live Activity의 상태를 계산해 그대로 푸시로 보내자.”
Trace는 별도의 서버 인프라 대신 Firebase를 사용하고 있기 때문에, 이 구조를 Firebase Functions + Firestore만으로 비교적 간단하게 구현할 수 있었습니다.
Live Activity를 ‘실시간처럼’ 업데이트하려면 필요한 것은 두 가지뿐입니다.
- 1. 지금 어떤 일정이 진행 중인지 계산하는 곳
- 2. 그 결과를 Live Activity로 직접 밀어 넣을 통로
📌 전체 데이터 흐름
iOS App
└─ (토큰 + 일정 저장)
↓
Firestore
↓
Firebase Functions
└─ 현재 이벤트 계산
↓
APNs – ActivityKit Push
└─ contentState 전송
↓
└─ UI 자동 업데이트
1) iOS
Live Activity 생성 시 자동으로 발급되는 liveActivityToken 획득
시간 기반 일정 목록을 Firestore에 저장
이 정보만으로 서버는 “어떤 기기에, 어떤 일정 기준으로 Live Activity를 업데이트해야 하는지”를 알 수 있습니다.
2) Firebase Functions
Functions는 매 분 실행되며 다음을 수행합니다.
- 1. Firestore에서 현재 유효한 디바이스 목록 조회
- 2. 각 디바이스의 이벤트 중 현재 시간 기준으로 ‘지금 보여줘야 할 이벤트’ 선택
- 3. 해당 이벤트를 contentState에 담아 liveActivityToken으로 ActivityKit Push 전송
// ActivityKit Push 전송
const notification = new apn.Notification(); notification.topic = "com.example.app.push-type.liveactivity"; notification.pushType = "liveactivity"; notification.event = "update"; notification.contentState = { event: currentEventData, // 지금 보여줄 이벤트
timestamp: Date.now(),
status: "Active"
};
await apnsProvider.send(notification, liveActivityToken);
3) 작동 영상
서버가 contentState를 ActivityKit Push로 전달하고, Live Activity가 이를 즉시 반영하는 전체 흐름이 실제로 어떻게 작동하는지 보여줍니다.
Push to Start
지금까지의 Live Activity 구조에는 한 가지 전제가 있었습니다.
Live Activity는 사용자가 직접 앱에 들어와 트리거해야만 시작된다는 점입니다.
하지만 저희는 여기서 한 걸음 더 나아가고 싶었습니다.
“앱을 켜지 않아도 Live Activity가 자동으로 시작될 수는 없을까?”
바로 이 부분을 가능하게 해주는 기능이 iOS 17.2부터 도입된 pushToStartToken 입니다. pushToStartToken 을 사용하면 서버에서 Live Activity를 원격으로 시작할 수 있습니다.
즉, 사용자가 앱을 열지 않아도:
중요한 일정이 곧 시작될 때
할일이 시작되기 직전
사용자가 하루 동안 앱을 전혀 켜지 않았을 때도
잠금화면에 Live Activity를 자동으로 띄우는 경험을 만들 수 있습니다.
참고로 Apple 문서상으로는 Activity를 생성하지 않아도 pushToStartTokenUpdates에서 토큰을 받을 수 있다고 설명하고 있습니다. 하지만 iOS 17.x에서는 이 동작이 상당히 불안정했습니다. 여러 개발자들이 동일한 문제를 겪었다는 보고도 있었고 Apple 역시 관련 이슈를 인지하고 있는 것으로 알려져 있습니다.
이 때문에 Trace는 앱 실행 시 사용자에게 보이지 않는 ‘더미 Activity’를 잠시 생성해 토큰을 확보하는 방식을 채택했습니다. (iOS 18에서는 이 동작이 개선되었다고 합니다.)
📌 전체 데이터 흐름
iOS App
└─ (pushToStartToken + 시작 시각 저장)
↓
Firestore
↓
Firebase Functions
└─ 시작 시점 도달 여부 확인
↓
APNs – Push to Start
└─ attributes + contentState 전송
↓
└─ 앱을 열지 않아도 자동 생성
Push to Start 토큰을 얻고 나면 전체 구조는 매우 단순합니다.
1) iOS
앱 실행 시 더미 Activity를 통해 pushToStartToken 획득 pushToStartToken과 일정 시작 시각을 Firestore에 저장
2) Firebase Functions
Functions는 매 분 실행되며 다음을 수행합니다.
“지금 Live Activity를 시작해야 할 시점인가?”를 확인합니다.
시점이 도달하면 APNs에 pushToStartToken으로 Push to Start 요청을 보냅니다. 이때, Live Activity에 처음 표시될 attributes와 contentState도 함께 전달합니다.
// Push to Start - Activity 생성용 Push notification.event = "start"; // "update"가 아닌 "start" notification.attributes = { // 생성 시에만 필요
startTime: Date.now()
};
notification.contentState = {
title: "러닝 30분",
eventDate: 1733458200,
status: "active"
};
즉, 업데이트 Push와 마찬가지로 서버가 화면에 보여줄 데이터를 모두 계산해서 한 번에 보내는 구조입니다.
3) 작동 영상
서버가 pushToStartToken을 사용해 Live Activity를 원격으로 생성하고, 초기에 표시될 attributes, contentState를 보내 잠금화면에 자동으로 나타나는 과정을 확인할 수 있습니다.
마무리
Live Activity를 도입한 이후 Trace의 핵심 지표에도 변화가 있었습니다.
특히 Day0 → Day1 리텐션이 약 10% 상승하며 초기 경험의 개선 효과가 뚜렷했습니다.
사용자들은 “앱을 열지 않아도 잠금화면에서 바로 체크할 수 있어 편하다”, “할 일을 Live Activity에서 바로 완료할 수 있는 게 좋다”는 피드백을 주었습니다.
Trace는 앞으로도 AI와 iOS 생태계의 기능들을 깊이 있게 탐구하며, 사용자의 하루에 자연스럽게 스며드는 경험을 만들어갈 예정입니다.
이번 Live Activity 구축 과정은 팀에게도 매우 흥미로운 도전이었고, 저 개인적으로도 많은 인사이트를 얻은 경험이었습니다. 이 글이 비슷한 문제를 고민하는 분들께 작은 참고점이나 도움이 되었으면 좋겠습니다.
감사합니다.
참고 자료
Starting and updating Live Activities with ActivityKit push notifications — Apple Developer Update Live Activities with push notifications — WWDC23 pushToStartToken — Apple Developer https://developer.apple.com/forums/thread/805324