Intro
- OTA 업데이트를 배포해도 사용자가 앱을 다시 시작하지 않으면 새 기능이 묻히고는 했습니다.
- 저는 Expo Updates를 사용해 앱이 켜질 때와 포그라운드로 복귀할 때 업데이트를 확인하고, 모달로 바로 알려주도록 만들었습니다.
핵심 아이디어 요약
Updates.checkForUpdateAsync와 Updates.fetchUpdateAsync를 조합해 백그라운드에서 새 번들을 내려받습니다.
AppState 변화를 구독해 포그라운드 복귀 시에도 업데이트를 체크합니다.
- 재시작 버튼을 누르면
Updates.reloadAsync로 즉시 새 번들을 적용하고, 취소할 수도 있게 했습니다.
준비와 선택
- OTA는 배포 채널과 runtimeVersion이 맞아야 하므로 fetch 결과가
isNew일 때만 모달을 띄우도록 했습니다.
- 모달은 이미 사용 중인
BaseModal 컴포넌트를 재활용해 디자인 시스템을 따랐습니다.
- 로딩 상태를 보여주기 위해
ActivityIndicator를 추가해 사용자가 다운로드 진행 중임을 알 수 있도록 했습니다.
구현 여정
- 체크 함수 분리:
check 함수를 useCallback으로 분리해 최초 렌더와 AppState 이벤트에서 재사용했습니다.
- AppState 구독:
AppState.addEventListener('change', ...)로 포그라운드 복귀 이벤트를 감지했습니다.
- 다운로드 진행 표시:
setIsFetching(true)로 상태를 표시한 뒤, 요청이 실패하더라도 finally에서 false로 되돌렸습니다.
- 재시작 로직:
Updates.reloadAsync를 호출하기 전 모달을 닫아 사용자 경험을 매끄럽게 했습니다.
- 에러 처리: 네트워크 에러는 사용자에게 굳이 노출하지 않고 로그에만 남겼습니다.
export default function OtaUpdateModal() {
const [visible, setVisible] = useState(false);
const [isFetching, setIsFetching] = useState(false);
const check = useCallback(async () => {
try {
const res = await Updates.checkForUpdateAsync();
if (res.isAvailable) {
setIsFetching(true);
const fetched = await Updates.fetchUpdateAsync();
if (fetched.isNew) setVisible(true);
}
} catch {
} finally {
setIsFetching(false);
}
}, []);
useEffect(() => {
check();
const sub = AppState.addEventListener('change', state => {
if (state === 'active') check();
});
return () => sub.remove();
}, [check]);
const restartNow = async () => {
setVisible(false);
await Updates.reloadAsync();
};
return (
<BaseModal isOpen={visible} onClose={() => setVisible(false)}>
<View style={tw`flex w-80 flex-col items-center gap-4 px-5 pb-4 pt-6`}>
<Text style={tw`text-gray-10 t-l-b`}>업데이트가 준비됐어요</Text>
{isFetching ? (
<View style={tw`flex flex-row items-center gap-2 py-2`}>
<ActivityIndicator color={colors.primary[50]} size='small' />
<Text style={tw`text-gray-50 t-s-r`}>업데이트 준비 중...</Text>
</View>
) : (
<View style={tw`flex w-full flex-col items-center gap-3`}>
<BaseButton label='지금 재시작' onPress={restartNow} size='sm' />
<BaseButton12
color='gray'
label='나중에 할게요'
variant='text'
onPress={() => setVisible(false)}
/>
</View>
)}
</View>
</BaseModal>
);
}
결과와 회고
- 업데이트가 있을 때 사용자들이 평균 10분 안에 새 버전으로 재시작하면서 새로운 기능 배포 속도가 빨라졌습니다.
- 다운로드 실패 상황에서도 UI가 조용히 복구돼 불필요한 문의가 줄었습니다.
- 향후에는 릴리즈 노트나 변경사항 요약을 모달에 함께 보여주는 실험을 해보려 합니다.
- 다른 팀에서는 OTA 알림을 어떻게 설계하고 계신가요? 댓글로 공유해 주세요.
Reference