Post

KW Pass 개발기

Wear OS를 지원하는 광운대학교 도서관 출입증 애플리케이션 개발기

KW Pass 개발기

TL;DR
광운대 도서관 출입증 QR을 스마트폰/Wear OS에서 빠르게 꺼내볼 수 있는 앱을 만들었다.
Phone/Wear OS/Shared 3모듈로 분리하고, AuthKey 캐싱으로 QR 요청 단계를 3→1로 최적화했다.
첫 배포에서 비공개 테스트/심사 프로세스를 제대로 경험했고, 유지보수 가능한 설계의 중요성을 체감했다.

KW Pass 앱 아이콘

출시 배경

 내가 처음 대학교에 들어가면서 실사용자가 있는 서비스를 만들어보고 싶은 마음이 있었다. 그리고 이런 실사용자를 가장 쉽게 가져올 수 있는 방법은 내가 속한 학교 구성원을 대상으로 한 서비스를 만드는 것이라고 생각했다. (비록 만 명도 안 되더라도!)

 그렇게 만든 것이 학교 친구와 함께 시작한 KW Friends(광운 모이미) 앱이었다. 학교 선배님의 멘토링을 받으면서 시작한 첫 안드로이드 Compose 프로젝트였다. 첫 개발임에도 하나의 모임 플랫폼을 만들어야 했고, 다양한 아키텍처 설계를 고려해야 했다. 다만 첫 프로젝트였고, 우리는 사전 지식이 너무나 부족했다. 

  • 뷰모델의 UiState에 들어가야 할 요소들 넣기
  • 의존성 주입 없이 모든 곳에서 하나의 초-거대 뷰모델에 의존하는 구조
  • 네비게이션 대신 거대한 if-else문으로 화면 전환하기
  • 디자인 패턴이 없어 모든 페이지마다 다른 인터페이스 색상과 폰트 크기
  • 라이트/다크 모드 미구현
  • 멀티태스킹 기능으로 화면 크기를 바꾸면 발생하는 크래시(??)

 말 그대로 난장판인 코드 덩어리였다. 그렇게 마지막 멘토링에서 “앱 아키텍처를 완전히 갈아엎어야 한다”는 말을 들었지만, 입대가 얼마 남지 않은 시점에서 결국 이 프로젝트는 무산되었다.

 내가 군 생활하면서 앞으로의 미래에 대해 많은 생각을 했었지만, 전역 후 몇 년간은 치열하게 살면서 경험을 쌓아야 한다는 결론에 도달했다. 전역하고 한 달 정도 쉬고 나서 “이제 현생으로 돌아가야 한다”는 생각이 들었고, 1학년 때의 실패를 이어서 두 번째 프로젝트를 기획하게 되었다.

 과거 멘토링을 해주셨던 학교 선배님이 ‘KW Pass’라는 이름으로, 학교 도서관 출입증 QR 코드를 워치에서 볼 수 있게 했던 애플리케이션이 있었다. 구글 플레이에 버전 1.0으로 업로드가 되어 있었다. 하지만 이 앱은 워치 standalone 애플리케이션이었고, 변경된 구글 플레이 정책(워치 단독 로그인 제한 등)으로 인해 더 이상의 업데이트 및 배포가 어려운 상태였다.    마지막 멘토링 때 “휴대폰 컴패니언 앱을 만들어보면 어떻겠냐”는 조언을 들었던 것이 떠올랐고, 이렇게 된 거 내가 직접 새로 만들어보자는 결론에 도달했다.


개발 과정

 하루에 1~3시간 동안 매일 개발했다. 지금 생각해보니 투자 시간을 늘렸다면 출시 시간을 크게 앞당길 수 있었을 것 같았다. 

1주차 - 기반 로직 구현

 가장 기본적인 QR 코드 요청 로직을 만들어야 했다. 기존에 다른 학교 선배님께서 구현한 로직을 코틀린으로 옮겼고, 기존 ‘KW Pass’의 구조도 일부 참고했다. 또한, KW Friends에서 “아키텍처 부재”의 아픔을 느꼈기에, 이번에는 초반부터 아래 원칙을 잡고 갔다.

  • Hilt로 의존성 주입 적용
  • UiState를 분리하고 상태 기반 UI 패턴을 제대로 사용

2주차 - 핵심 로직 구현 (확장성을 고려한)

 워치 연동을 준비하며 폰에서 테스트를 진행하던 중, 중요한 기획적 전환점을 맞이했다. 단순히 워치 로그인을 돕는 컴패니언 앱을 넘어, Wear OS가 없는 사용자들도 스마트폰만으로 사용할 수 있는 독립적인 서비스로 확장하기로 했다.    기술적으로는 이를 위해 프로젝트 구조를 Phone / Wear OS / Shared 3개의 모듈 구조로 분리했고, 공유 로직을 Shared로 모아 재사용성과 유지보수성을 확보했다. 워치에서는 단순 QR 기능을 넘어 워치 페이스에 컴플리케이션을 추가하여 앱 바로가기를 항상 빠르게 접근할 수 있도록 했다.

 워치 앱은 기능이 단순한 편이라, 대신 폰 앱에서는 디자인을 특히 신경 썼다. Material 3 가이드를 최대한 따라가며, 첫 실행 시 환영 인사와 함께 간단한 사용법을 안내하는 온보딩 흐름을 넣었다.

 이 시점까지 앱 아이콘이 없어서 AI로 만들어보려고도 했지만, 원하는 방식(팔레트 반영 + 벡터 기반)으로 뽑기가 어려웠다. 결국 일러스트레이터를 다룰 줄 아는 사촌 누나께 부탁드렸고, 흔쾌히 아이콘을 제작해 주셨다. (감사합니다…!)

3주차 - 리팩토링과 내실 다지기

 이때 2주 동안 가족과 함께 긴 여행을 다녀왔다. 개발 활동은 하지 못했지만, 그동안 내 앱에서 부족한 점에 대해 오랫동안 생각해볼 수 있었다.

  • 위젯화면 스와이프로 닫기 구현
  • 인터넷 연결 안될 때 에러 핸들링
  • 개인정보처리방침 만들기
  • Static Shortcuts 만들기
  • Firebase Crashlytics 추가
  • QR 리포지토리에서 에러 핸들링 패턴 강화
  • AuthKey 캐싱 및 재사용으로 불러오기 시간 단축
  • 거대한 MainUiState 하위 UiState로 분리
  • MainViewModelUseCase로 나누기
  • 등등

 개발 중에는 “지금이 최선”이라고 생각했던 구조들도, 잠깐 거리를 두고 보니 빈틈이 많이 보였다. 여행을 마치고 돌아와서 Crashlytics와 에러 핸들링 강화까지 마친 다음, 바로 비공개 테스트를 진행했다.

4주차 - 테스트 및 최적화

 프로덕션 신청까지 2주의 긴 시간과 12명의 테스터가 필요했다. 이 조건들을 맞추는 데 시간이 꽤 걸리기에 테스트 기간을 지속적으로 업데이트를 배포하며 완성도를 올리는 시간으로 쓰기로 했다.    폰/워치의 두 가지 트랙으로 비공개 테스트를 진행해야 했고, 나머지 여행 중에 생각난 개선점들은 비공개 테스트를 진행하면서 해결해 나갔다.

 이때 1.1버전 업데이트에서 큰 개선이 하나 있었다. QR 코드 요청 로직에서 캐싱을 적용해 (캐시가 유효한 경우) 요청 단계를 3단계 → 1단계로 줄였다. 그 결과 체감 속도가 약 3배 개선됐다.

처음에 QR 리포지토리를 코틀린으로 작성하면서 캐싱이 가능한지에 대한 테스트를 진행했었다. 이때 테스트 코드에서 변수명을 잘못 사용한 이유로(…) 캐싱이 불가능하다고 결론 내렸지만, 다시 검증해보니 캐싱을 통해 요청 속도를 3배 빠르게 단축할 수 있음을 알게 되었다.

5주차 ~ 6주차 - 프로덕션 승인 거절

 1월 23일 저녁에 프로덕션 심사를 신청했다. “구글 플레이 심사는 앱스토어보다 널널하다”는 인식이 있어서 큰 문제는 없을 거라 생각했는데, 1월 26일 새벽 2시에 거절 메일이 와서 잠에서 깼다.

프로덕션 승인 거절 메일

 억울한 마음도 있었지만, 다시 생각해보면 당연한 결과였다. 방학이라 학교를 가는 테스터가 거의 없었고, 학교 구성원이 아닌 지인들은 로그인 자체가 어려워 앱을 제대로 실행하지 못했다. 즉, 많은 테스터 기기에서 실사용이 충분히 발생하지 않았고, 구글 플레이에서 이를 근거로 프로덕션 신청을 거절한 것이다. 지인들에게 “매일 앱을 실행해 달라”라고 부탁하기도 현실적으로 어려워서, 외부의 비공개 테스터들의 도움을 받아 테스터를 늘렸다.

 두 번째 비공개 테스트에서도 3일 간격으로 미뤄왔던 컴포즈 네비게이션 간 애니메이션, 오타 수정, UI 디테일 다듬기 등의 업데이트를 계속 업로드하였고, 덕분에 사소하게 보였던 앱의 빈틈도 점차 채워져 나갔다.

7주차 - 출시

 2차 비공개 테스트를 완료한 뒤, 1차 때보다 더 꼼꼼하게 프로덕션 신청 설문을 작성했다. 그리고 신청 다음 날 아침, 승인 메일을 받았다!

프로덕션 승인 메일

 바로 비공개 테스트 트랙을 프로덕션 트랙으로 승급했고, 그렇게 내 인생 첫 앱이 구글 플레이에 업로드되었다. 어느 기기에서든 구글 플레이에 “kw pass”를 검색하면 내 앱이 뜨는 걸 보고 정말 설렜다. 첫 배포의 짜릿함을 온몸으로 느낀 순간이었다.

구글 플레이 스토어 스크린샷


느낀 점

 이번 프로젝트에서 가장 크게 느낀 건, 미래의 유지보수를 고려한 설계가 정말 중요하다는 점이었다. AuthKey 캐싱을 구현하면서 Shared 모듈에서 구현한 리포지토리와 UseCase를 리팩토링했는데, 폰/워치가 로직을 공유한 덕분에 순식간에 작업을 마칠 수 있었다.    또한 Material 3 디자인 패턴을 그대로 따라간 덕분에 버튼/컴포넌트를 추가할 때마다 UI 설계에 고민하기보다 일관된 UI를 유지할 수 있었다. 

 개발 중에 AI의 도움도 많이 받았다. 기존에는 궁금한 점을 구글에 일일이 검색하고 Stack Overflow나 블로그 글을 여럿 읽으면서 천천히 구현해야 했지만, 이번에는 궁금한 점을 바로 AI에 질문했다. 아키텍처를 어떻게 설계해야 리소스를 적게 사용하고 안정성 있는 구조를 만드는지, 코드의 어느 부분에서 I/O 병목이 발생할 수 있는지, 보안상으로 문제가 될 수 있는 요소가 있는지 등 다양한 분야에서 조언을 받을 수 있었다. 물론 내 스스로 다른 사람이나 AI가 작성한 코드를 그대로 가져오는 것 대신, 참고한 코드는 내가 내 언어로 해석하고 이해하려고 노력했다.

 주로 Gemini 3 Pro/Flash, Claude 4.5 Sonnet/Opus를 사용했다. GitHub 프로젝트를 웹에서 바로 불러올 수 있는 점은 Gemini가 특히 편했다. Opus는 출력 품질이 좋았지만, 긴 컨텍스트 질문을 몇 번 하면 사용량 제한에 걸려 아쉬웠다. “매일 일정 횟수를 보장”한다는 점에서는 Gemini 쪽이 내 사용 패턴에 더 잘 맞았다.

 이것이 내 첫 프로젝트로, 중간에 가족여행 기간을 제외하면 약 2달 동안 열심히 배우고 개발했다.(테스트가 1달 걸려버렸다!) 단순해 보이는 기능에도 고려할 요소가 많다는 걸 체감했고, 그런 요소들을 습관적으로 챙기는 개발자가 되기 위해 더 노력해야겠다고 느꼈다. 이제 복학 전까지 소프트웨어 마에스트로 준비하고 다른 프로젝트 아이디어를 생각하면서 보낼 예정이다.

KW Pass

주요 기능

  • 다국어 지원 (한국어, 영어, 일본어, 베트남어, 러시아어, 중국어 간체, 중국어 번체)
  • Phone: QR 코드 위젯 지원
  • Phone: (삼성 갤럭시 기기의 경우) 모드 및 루틴 앱 등을 통해 QR 코드 실행 자동화
  • Watch: 컴플리케이션을 통해 빠른 QR 코드 확인
  • Watch: 휴대폰에 로그인된 계정과 연동

링크

Tech Stack

  • Kotlin
  • Jetpack Compose
  • ZXing
  • Retrofit2 & OkHttp3
  • TikXml
  • Wearable Data Layer API
  • Hilt
  • MVVM

첫 블로그 글입니다. 제가 아직 글 쓰는 능력이 그리 좋지 않다는 게 느껴지네요. 읽어 주셔서 감사합니다.

This post is licensed under CC BY 4.0 by the author.