5개 워치페이스를 유료판으로 재패키징하다
목차
5개의 워치페이스를 새로운 패키지명으로 통일하고 버전을 초기화해서 유료 재출시를 준비했다. 각 build.gradle.kts 파일을 돌면서 namespace를 com.hedvion.watchface.<face>pro 패턴으로 변경하고, versionCode를 1로 설정한 거다. 이건 단순한 빌드 설정 변경처럼 보이지만, 제품 전략과 기술 관리가 얽혀 있는 결정이었다.
왜 패키지명을 바꿔야 했나
안드로이드 앱스토어에서 같은 애플리케이션 ID(package name)는 한 번 출시되면 다시 사용할 수 없다. 무료 버전이 이미 배포된 상태라면, 유료 재출시는 새로운 독립적인 애플리케이션으로 취급되어야 한다. 이게 핵심이었다.
기존 package name을 그대로 쓰고 가격만 올리면, 사용자 입장에선 기존 무료 앱을 유지하면서 업데이트를 받지 않게 된다. 반대로 새로운 package name으로 완전히 다른 앱으로 출시하면, 고객 입장에선 "같은 워치페이스인데 왜 따로 사야 돼?"라는 의문이 생긴다. 하지만 마켓플레이스 정책과 라이센싱 모델을 감안하면, 이게 정석적인 접근이다.
그리고 .pro suffix를 붙인 건 사용자에게도 명확한 신호를 주기 위함이다. 설정 화면에서 설치된 앱 목록을 보면 "crimson", "graphite" 같은 일반 이름과 "crimson pro", "graphite pro"를 구분할 수 있다. 브랜드 일관성도 있고.
5개 파일을 일관되게 처리하는 게 중요했던 이유
Crimson, Graphite, Ivory, Noir, Slate—5개의 서로 다른 워치페이스가 있었다. 각각 독립적인 build.gradle.kts를 가지고 있어서, 한 줄씩 빠뜨리면 일관성이 깨진다.
예를 들어, Slate만 실수로 .pro suffix를 빼면, CI/CD에서 빌드는 성공하겠지만, 배포 후 마켓플레이스에서 package name이 충돌하거나, 사용자가 "이 앱들이 같은 브랜드인데 왜 이름이 다르지?"라는 혼란을 겪는다. 특히 광고나 마케팅 자료에서 "5개의 프리미엄 워치페이스"라고 홍보할 때, 내부 package name의 일관성은 버그 리포트, 기술 지원, A/B 테스트 추적까지 영향을 미친다.
그래서 이 작업은 단순한 find-and-replace가 아니라, 5개 파일을 검토하고 일관성을 확인하는 프로세스였다. 팀이 커질수록 이런 반복적인 작업은 휴먼 에러 가능성이 높아지는데, 우리 팀에서는 코드 리뷰 때 "모든 face에 대해 naming 패턴이 일관적인가?"를 체크리스트에 추가했다.
versionCode 1과 versionName 1.0.0의 의미
versionCode를 1로 설정한 건 이게 새로운 제품 라인의 시작임을 나타낸다. 만약 기존 무료 버전이 versionCode 15였다면, 유료판의 versionCode는 1부터 시작하는 게 맞다. 그 이유는:
- 사용자 혼동 방지: 버전 번호가 높다고 해서 "더 새로운 앱"이라고 착각하지 않도록
- 배포 정책 명확화: 마켓플레이스 입장에서 무료/유료는 별개의 애플리케이션이고, 각각 독립적인 버전 히스토리를 가짐
- 팀 커뮤니케이션: "이건 pro의 1.0.0"이라고 하면, 모두가 "유료 첫 출시"를 이해함
versionName은 사용자가 설정 앱에서 보는 버전 문자열이다. 1.0.0 형식은 semantic versioning을 따르는 거고, 이후 업데이트는 1.0.1, 1.1.0 같은 식으로 관리할 수 있다.
회고: 규모가 작으면 자동화를 고민하자
5개 파일이라는 규모는 손으로 충분히 관리할 수 있었지만, 앞으로 워치페이스가 10개, 20개로 늘어나면? 그때마다 수동으로 5군데를 고치는 건 비효율적이고 위험하다.
이 작업을 하면서 팀에 제안한 게, shared build configuration 또는 namespace 생성 스크립트를 두는 거였다. Gradle의 subproject 구조를 활용하면, 각 face의 고유 이름만 정의하고 나머지는 템플릿으로 생성할 수 있다. 예를 들어:
// buildSrc/src/main/kotlin/WatchFaceConfig.kt
object WatchFaceConfig {
fun getPackageName(faceName: String, isPro: Boolean) =
"com.hedvion.watchface.$faceName${if (isPro) ".pro" else ""}"
}
물론 지금 당장 필요한 건 아니지만, 이렇게 "변경의 영향 범위"를 먼저 파악하고 나중을 대비하는 게 팀장으로서의 책임이라고 생각한다. 단순히 "이번 주 할 일" 이상으로 "다음 분기가 어떻게 복잡해질지"를 미리 예측하는 거다.
또 다른 배운 점은, 이런 구조적 변경을 할 때는 한 번에 여러 변경을 섞지 않는 게 좋다는 것. 이번엔 "package name + version reset"만 했는데, 만약 여기다 dependency 업그레이드나 기타 gradle 설정을 함께 했다면, 문제가 생겼을 때 원인 파악이 훨씬 어려웠을 거다. 원자적(atomic)인 커밋을 유지하는 게 얼마나 중요한지 다시 한 번 느꼈다.
🛒 이 글과 어울리는 추천 상품
*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.
댓글 0
첫 댓글 달아줘.