나는 2022년부터 Expo를 사용하여 크로스 플랫폼 앱을 개발해왔다. 2024년부터 Expo 가 React Native의 표준 프레임워크로 자리잡으면서 안정성이 높아졌고, 네이티브 제약도 꽤 많이 해소되었다.
이 글에서는 내가 작년부터 3개의 앱에 iOS와 Android 위젯을 도입하여 운영해 본 경험을 공유하려 한다. 이제는 AI를 사용하여 Kotlin, Swift 코드를 직접 작성하지 않아도 손쉽게 네이티브 위젯을 만들 수 있다.
위젯의 필요성
위젯은 사용자가 앱을 직접 열지 않아도 홈 화면에서 핵심 정보를 바로 확인할 수 있게 해준다. 특히 시시각각 변하는 해와 달의 위치를 보여주는 Golden Horizon 앱에서는 가장 핵심적인 기능이다.
참고 링크: Apple Home Screen Widgets (Evan Bacon)
라이브러리 선정 — 종속성과 안정성에 대한 깊은 고민
처음에는 두 플랫폼을 모두 지원하는 @bittingz/expo-widgets 라이브러리를 사용하여 위젯을 구현했다. 하나의 라이브러리로 iOS와 Android를 모두 커버할 수 있다는 점은 매력적이었지만, 얼마 지나지 않아 Expo SDK 버전을 올리면서 빌드 오류가 발생하기 시작했다. 이는 내가 Expo를 사용하면서 가장 스트레스를 받는 지점 중 하나인데, React Native가 아직 메이저 버전이 0.x.x 단계여서 브레이킹 체인지가 빈번하게 발생하기 때문이다.
iOS: @bacons/apple-targets
그러던 중 Expo의 엔지니어링 매니저인 Evan Bacon이 만든 @bacons/apple-targets의 출시 소식을 알게 되었다. 메인테이너가 Expo 코어 팀의 일원인 만큼, 향후 Expo SDK 업데이트에 따른 유지보수 대응이 훨씬 빠르고 안정적일 것이라 판단하여 iOS는 이 라이브러리로 마이그레이션하기로 결정했다.
이 라이브러리의 핵심은 targets/ 디렉토리에 Swift 파일을 넣어두면
prebuild 시 자동으로 Xcode 타겟이 생성된다는 점이다. 위젯 UI는 SwiftUI로 직접
작성해야 하지만, 타겟 설정과 빌드 파이프라인을 Expo가 관리해주기 때문에
네이티브 개발의 진입 장벽을 크게 낮춰준다.
Android: react-native-android-widget
Android 쪽은 유지보수가 비교적 활발하게 이루어지고 있는
react-native-android-widget을 선택했다. 이 라이브러리의 가장
큰 장점은 React Native 컴포넌트로 위젯 UI를 작성할 수 있다는 점이다.
FlexWidget, TextWidget,
ImageWidget 같은 위젯 전용 컴포넌트를 조합해서 레이아웃을 만들고,
Task Handler에서 생명주기 이벤트를 처리한다.
각 플랫폼별 개발 방법 및 주요 기술 과제 해결
이번 섹션에서는 특정 앱의 구현 로직을 떠나 위젯 개발 과정에서 겪었던 전반적인 기술적 난관과 해결 방법을 정리했다. 돌이켜보면 외부 라이브러리에만 의존하지 않고 로컬 커스텀 네이티브 모듈(Local Expo Module)을 적극적으로 만들어 활용한 것이 플랫폼의 제약을 우회하고 안정성을 높이는 데 가장 큰 도움이 되었다.
1. 백그라운드 갱신 전략
위젯은 시시각각 변하는 데이터를 표시해야 하므로 백그라운드 갱신이 필수적이다.
iOS는 시스템 타임라인을 활용하여 향후 데이터 변화 스케줄을
미리 제공하고 OS가 알아서 갱신하도록 설계하는 것이 효율적이었다. 반면
Android는 자체적인 스케줄링이 필요해
WorkManager를 활용하여 백그라운드에서 주기적으로(예: 최소 15분)
갱신 작업을 수행하도록 구현해야 했다.
2. 앱 데이터 동기화와 API 키 주입 (widget-storage 개발)
앱 본체와 위젯 프로세스는 서로 분리되어 있어 AsyncStorage 등을
공유할 수 없다. 이를 해결하기 위해 iOS의
App Group UserDefaults와 Android의
SharedPreferences를 공통으로 묶어주는 로컬 커스텀 네이티브
모듈(widget-storage)을 직접 개발했다. 앱에서 필요한 데이터나
사용자 상태를 저장하면 위젯이 이를 즉시 읽어가는 구조를 만들었다.
API 키와 같은 민감한 환경 변수 주입도 비슷했다. iOS는 빌드 타임에
Info.plist에 직접 주입하고, Android는
widget-storage 모듈을 통해 런타임에 안전하게 값을 넘겨주었다.
3. UI 구현 방식의 차이
iOS는 WidgetKit을 통해 UI를 SwiftUI로 직접 작성해야
했고, Android는 react-native-android-widget을 통해 React
컴포넌트(FlexWidget 등)로 레이아웃을 구성했다.
플랫폼별로 지원하는 UI 요소의 한계(예: 안드로이드 위젯의 복잡한 그라디언트 미지원 등)를 극복하기 위해, 코드로 구현하기 까다로운 레이아웃이나 그래픽 요소는 차라리 미리 만들어진 SVG 이미지를 활용하는 방식으로 우회하여 일관된 퀄리티를 유지했다.
4. 캐싱과 불필요한 API 호출 방지
네트워킹이 불안정할 때 위젯이 텅 비어버리는 현상을 막기 위해서 철저한 캐시 전략이 필수적이었다. 앱과 위젯 간에 캐시 데이터를 공유하고, "비슷한 조건(예: 동일한 날짜와 위치)"일 경우 기존 캐시를 최우선으로 재사용했다. API 호출에 실패하더라도 폴백(fallback)으로 이전 위젯 화면을 그대로 표시하여 사용자 경험을 해치지 않게 설계했다.
위젯 1년 운영 후기
- 네이티브 생태계에 대한 이해 필수: AI의 도움으로 Kotlin이나 Swift 코드를 직접 처음부터 끝까지 작성할 일은 크게 줄어들었다. 하지만 네이티브 개발이 어떤 원리로 돌아가는지, Xcode나 Android Studio 같은 에디터를 다루고 디버깅하는 방법은 숙지하고 있어야 위기를 모면할 수 있었다.
- 플랫폼별 네이티브 기능 학습의 기회: 각 플랫폼(iOS, Android)이 제공하는 네이티브 모듈의 특성을 명확히 아는 것이 중요했다. 이번 프로젝트를 통해 꺼져있는 앱을 부팅하여 깨우거나, 백그라운드 환경에서 UI를 최신 상태로 업데이트하는 생명주기 관련 지식을 크게 넓힐 수 있었다.
- 크로스 플랫폼의 한계와 아쉬움: Expo라는 훌륭한 크로스 플랫폼 프레임워크를 쓰고 있음에도, 위젯만큼은 여전히 각 플랫폼별로 네이티브 코드를 작성하고 UI도 완전히 분리해서 구현해야 한다는 점이 가장 큰 아쉬움으로 남는다.
Expo 환경에서 크로스 플랫폼 위젯을 만드는 것은 아직 "한 번 작성하면 두 플랫폼에서 모두 동작한다"는 이상과는 거리가 멀다. 하지만 Expo의 빌드 시스템과 Config Plugin 생태계 위에서 작업할 수 있다는 것은 여전히 큰 장점이다. prebuild 한 번이면 네이티브 프로젝트가 생성되고, EAS Build로 배포까지 연결되는 흐름은 순수 네이티브 개발 대비 운영 비용을 확실히 줄여준다.
위젯은 단순한 부가 기능이 아니라, 사용자와 가장 가까운 홈 화면에 존재하는 브랜드의 접점이다. 앞으로는 iOS 잠금화면 위젯이나 헬스 앱 연동 등 또 다른 형태의 네이티브 기능들을 적극적으로 활용하며 생태계 확장을 이어나갈 계획이다.
