Expo를 사용한 지 4년이 넘었다. 처음에는 조금만 네이티브 영역으로 들어가도 직접 해결해야 할 일이 많았지만, 지금은 Config Plugin과 EAS 생태계가 발전해 웬만한 네이티브 기능도 Expo 프로젝트 안에서 다룰 수 있게 됐다.
최근에는 Sportling에 처음으로 Apple Watch와 Wear OS 지원을 추가했다. 홈 화면 위젯과 마찬가지로 워치 앱도 각 플랫폼의 네이티브 코드로 구현해야 했다. watchOS는 Swift와 SwiftUI, Wear OS는 Kotlin과 Compose를 사용했고 코드 작성에는 Claude Fable을 활용했다.
Apple Watch는 @bacons/apple-targets 덕분에 EAS Build까지 비교적
자연스럽게 연결할 수 있었다. 반면 Wear OS는 별도 프로젝트를 직접 빌드하고 Play
Console에 올려야 해서 배포 과정이 조금 더 까다로웠다. 이 글에서는 두 플랫폼을
Expo 앱에 연결하며 사용한 구조와 배포 차이를 정리했다.
Expo React Native 앱
├─ iPhone → WatchConnectivity → SwiftUI·WidgetKit
└─ Android → Wear Data Layer → Kotlin·Compose·Tile
1. Apple Watch: Expo 프로젝트에 네이티브 타깃 추가하기
Apple Watch 쪽은 @bacons/apple-targets를 사용했다. 이
라이브러리는 프로젝트 루트의 targets/에 둔 네이티브 코드를
prebuild 과정에서 Xcode 타깃으로 생성하고 연결해 준다. 덕분에 생성되는
ios/ 폴더를 직접 관리하지 않고도 Swift 코드를 Expo 프로젝트와
함께 유지할 수 있었다.
Sportling에서는 SwiftUI 워치 앱과 WidgetKit 위젯을 각각 타깃으로 만들었다. EAS
Build가 워치 앱과 위젯의 프로비저닝 프로파일까지 함께 생성하도록
appExtensions를 등록하니 기존 iOS 앱과 같은 빌드 흐름으로 묶을 수
있었다.
2. Wear OS: 독립 Gradle 프로젝트 만들기
Wear OS는 Expo가 별도 타깃으로 관리해 주지 않는다. 생성되는
android/ 안에 워치 모듈을 넣으면
expo prebuild --clean의 영향을 받기 때문에, 프로젝트 루트에
독립된 wear/ Gradle 프로젝트를 만들었다.
전체 화면 앱은 Kotlin과 Compose for Wear OS로 작성했다. 타일은 ProtoLayout
기반 TileService로 만들고, 워치페이스 컴플리케이션은
SuspendingComplicationDataSourceService에서 제공했다.
두 표면의 차이도 컸다. 타일은 앱이 레이아웃을 직접 구성할 수 있지만, 컴플리케이션은 텍스트나 이미지 같은 데이터만 제공하고 최종 표현은 사용자의 워치페이스가 결정했다.
이 과정에서는 Flutter 앱에 Wear OS를 붙여 Play Store에 배포한 글이 특히 도움이 됐다. 휴대폰은 크로스 플랫폼 프레임워크로 유지하고 워치 앱은 별도 네이티브 프로젝트와 트랙으로 배포한다는 구성이 Sportling과 비슷했다.
3. 휴대폰과 워치 사이에서 데이터 동기화하기
Apple Watch에서는 App Group이 iPhone과 워치 사이에서 직접 공유되지 않았다.
iPhone 앱이 WCSession.updateApplicationContext로 최신 JSON
스냅샷을 보내고, 장면 이미지는 transferFile로 전달했다. 워치 앱은
데이터를 워치 로컬 App Group에 저장한 뒤 WidgetKit 타임라인을 갱신했다.
Wear OS에서는 Data Layer의 DataItem과 Asset을
사용했다. WearableListenerService가 백그라운드에서 데이터를 받은
뒤 로컬에 저장하고 타일과 컴플리케이션의 갱신을 요청했다.
두 플랫폼 모두 변경 이벤트를 하나씩 보내지 않고, 현재 필요한 전체 상태를 스냅샷으로 전달했다. 워치에는 마지막 상태만 중요했기 때문에 이 방식이 재연결과 삭제 처리까지 가장 단순했다.
4. 배포 방식은 완전히 달랐다
Apple Watch 앱과 위젯은 iOS 앱에 포함된다. EAS Build에서 iOS 앱과 워치 타깃을 함께 서명하고, 생성된 하나의 빌드를 TestFlight와 App Store Connect로 배포하도록 구성했다.
Wear OS 앱은 별도 AAB였다. ./gradlew :app:bundleRelease로
빌드하고, EAS가 Android 휴대폰 앱에 사용하는 것과 같은 업로드 키로 서명했다.
휴대폰과 워치 AAB의 versionCode는 서로 겹치면 안 되므로 워치에
별도 번호 범위를 사용했다. 완성된 AAB는 Play Console의 Wear OS 전용 테스트
트랙에 올린 뒤 프로덕션으로 승격하도록 구성했다.
마치며
유용한 앱은 해당 OS가 제공하는 기능을 잘 살리는 앱이라고 생각한다. 휴대폰 앱 안에 모든 기능을 가두기보다 위젯, 워치와 컴플리케이션처럼 사용자가 자주 보는 표면으로 자연스럽게 확장할 때 앱의 가치도 커졌다.
Expo를 처음 사용했을 때와 비교하면 이제는 웬만한 네이티브 개발도 Expo 프로젝트 안에서 시도할 수 있게 됐다. 필요한 부분만 Swift와 Kotlin으로 만들고 EAS 및 Config Plugin과 연결할 수 있다는 점이 굉장히 좋았다.
Apple Watch는 이미 EAS 흐름에 함께 넣을 수 있었지만 Wear OS는 아직 빌드, 서명과 업로드를 별도로 관리해야 했다. 앞으로 Android 워치 연동도 더 활발해져 Wear OS 앱까지 EAS에서 한 번에 관리할 수 있게 되면 좋겠다.




