타이완메이트 수익화 + 성능 최적화: 광고와 결제를 넣으면서 앱을 더 빠르게 만든 이야기
AdMob 리워드 광고 + 7일 패스 인앱 결제를 PDCA로 설계·구현하고, 앱 전체 성능을 82점에서 98점까지 끌어올린 과정을 정리했다.
💰 수익화를 시작한 이유
타이완메이트의 핵심은 AI다. 메뉴판 사진을 찍으면 AI가 번역하고, 숙소를 추천하고, 여행 일정까지 짜준다. 문제는 이 모든 게 공짜가 아니라는 것. GPT-4o-mini API 호출 한 번에 약 ₩0.6~1이 든다. 하루에 유저 한 명이 20번 쓰면 하루 ₩20, 유저가 1,000명이면 하루 ₩20,000. 여행 시즌에 트래픽이 몰리면 API 비용만으로 적자가 날 수 있다.
AI 기능이 앱의 가치를 만들지만, 동시에 운영 비용의 대부분을 차지한다. 앱을 지속하려면 수익 구조가 필요했다.
그래서 광고 + 인앱 결제 이중 모델을 선택했다. 무료 유저는 리워드 광고를 보고 AI 기능을 사용하고, 광고가 불편한 유저는 ₩1,200짜리 7일 패스로 광고를 제거할 수 있다. 7일 패스의 순이익 ₩750이면 해당 유저의 API 비용 ₩90(7일, 하루 20회 기준)을 충분히 커버한다.
📐 PDCA 한 사이클로 끝냈다
이전 개발기에서 9번의 PDCA 사이클을 돌렸다면, 이번 수익화는 단 1회로 Match Rate 100%를 달성했다.
| Phase | 결과 |
|---|---|
| Plan | 12개 요구사항 정의 |
| Design | 17개 설계 항목 |
| Do | 8개 신규 파일 + 9개 수정 |
| Check | 100% (반복 0회) |
| Report + Archive | 완료 |
9번 반복했던 건 경험이 부족해서가 아니라, 기능이 계속 늘어났기 때문이다. 이번엔 기획서를 미리 꼼꼼히 작성해둔 덕분에 한 번에 끝낼 수 있었다.
🎬 광고 흐름: UX를 해치지 않는 설계
수익화에서 가장 중요한 건 **"유저가 짜증나지 않는 광고"**다. 다음 원칙을 세웠다.
원칙 1: 첫 3회 무료
앱을 처음 설치하면 AI 기능 3회까지 광고 없이 바로 쓸 수 있다. 기능의 가치를 먼저 체험해야 광고를 볼 의향이 생긴다.
원칙 2: 번역은 1시간 쿨다운
여행 중 번역은 자주 쓴다. 매번 광고를 보여주면 쓰기 싫어진다. 광고 한 번 보면 1시간 동안 무제한 번역. 여행자 입장에서 한 블록 돌아다니는 시간이다.
원칙 3: 광고 실패 시 그냥 통과
네트워크가 안 좋아서 광고가 안 뜨면? 그냥 통과. 유저가 기능을 못 쓰는 건 최악이다. 수익보다 경험이 우선.
AI 기능 사용 시도
→ 테스트 플래그 ON? → 통과
→ 7일 패스 활성? → 통과
→ 무료 3회 남음? → 통과 (카운트 -1)
→ [번역] 쿨다운 남음? → 통과
→ 광고 표시 → 실패해도 통과
🏗 구현 구조
서비스 계층
AdService — 광고 로드/표시/쿨다운 (feature별 분리)
PurchaseService — IAP 결제/복원/영수증 검증
MonetizationProvider — 상태 통합 + gateCheck()
gateCheck(context, AiFeature.translate) 한 줄이면 어떤 AI 기능이든 광고 게이트를 적용할 수 있다. 번역, 숙소, 일정 — 세 곳에 각각 삽입했다.
테스트 플래그
/// 🚨 배포 전 반드시 false로 변경! 🚨
const bool kForceDisableAds = true;이 값이 true면 광고도 결제도 전부 스킵된다. 개발 중에 광고 때문에 흐름이 끊기는 일 없이 자유롭게 작업할 수 있다. 배포 전에 false로 바꾸기만 하면 된다.
영수증 검증
7일 패스를 구매하면 앱이 Vercel API로 영수증을 보내고, 서버가 Apple/Google에 검증 요청을 한다. 검증 성공하면 만료 시각을 로컬에 저장한다.
Client → POST /api/v1/purchase/verify → Apple/Google 서버
→ 검증 성공 → 만료시각 저장
🚀 성능 최적화: 82점 → 98점
수익화를 넣으면서 "이왕 코드 건드리는 김에 전체 성능도 잡자" 했다. code-analyzer로 전체 앱을 분석했더니 생각보다 개선할 게 많았다.
불필요한 리빌드 제거 (High)
14개 화면 파일에 ref.watch(themeModeProvider)가 각각 들어 있었다. "테마 바뀌면 리빌드"하려는 의도였는데, 이미 루트 위젯(TaiwanMateApp)에서 테마를 감지하고 전체 트리를 리빌드한다. 자식 화면에서 또 감지하면 같은 이벤트에 15번 리빌드가 일어난다.
13개를 지웠다. 한 줄씩 지우는 건 쉬운데, "지워도 되는 건지" 판단하는 게 핵심이었다.
바텀 네비게이션 분리 (High)
6개 탭의 바텀 네비가 부모 위젯 안에 인라인으로 들어있었다. 키보드가 열리거나, 로케일이 바뀌거나, 어떤 이유로든 부모가 리빌드되면 ~40개의 Animated 위젯이 매번 재생성됐다.
_BottomNavBar를 별도 StatelessWidget으로 추출했다. 이제 부모가 리빌드되어도 바텀 네비는 currentIndex가 바뀔 때만 갱신된다.
IndexedStack으로 탭 전환 (Low → 체감 High)
기존에는 AnimatedSwitcher로 탭을 전환했다. 보기엔 예쁜 페이드 애니메이션이지만, 탭을 바꿀 때마다 이전 화면이 dispose되고 새 화면이 initState부터 다시 시작했다. 날씨 API 재호출, 스팟 데이터 재초기화, 음성인식 재설정...
IndexedStack으로 바꾸니 모든 탭이 메모리에 유지된다. 탭 전환이 즉시 일어나고, 이전에 보던 스크롤 위치도 그대로다.
나머지 최적화
| 이슈 | 수정 |
|---|---|
| Spots 데이터 매번 객체 생성 | final 캐싱으로 1회만 변환 |
| Spots 필터링 매 리빌드 | provider 내부 filteredSpots 캐싱 |
| Expense CRUD마다 전체 DB 재조회 | 인메모리 업데이트 (DB는 저장만) |
| Expense 날짜별 선형 스캔 | expensesByDate Map으로 O(1) 조회 |
| 날씨 API 도시 전환마다 중복 호출 | 10분 TTL 캐시 + race condition 방지 |
| 번역 이미지 2~10MB state 보관 | 파일 저장 후 state에서 해제 |
| 짐싸기 데이터 전체 키 순회 | JSON 방식 + 레거시 자동 마이그레이션 |
📊 최종 수치
| 항목 | Before | After |
|---|---|---|
| 수익화 | 없음 | AdMob + IAP 완성 |
| QA 점수 (수익화) | - | 90/100 |
| 앱 성능 점수 | 82 | 98 |
| flutter analyze | - | 0 errors, 0 warnings |
| 불필요한 리빌드 | ~15회/이벤트 | 1회/이벤트 |
| 탭 전환 | dispose+initState | 즉시 전환 (IndexedStack) |
| 날씨 API 호출 | 도시마다 매번 | 10분 캐시 |
| Expense DB 쿼리 | CRUD마다 2회 | 초기 1회만 |
🎯 회고
잘한 점
- 기획서를 먼저 꼼꼼히 썼다. 수익화 기획서 3개(기획서, 실행가이드, 개발자 작업 가이드)를 미리 정리한 덕에 PDCA 1회로 100%를 찍었다. 이전 9회 반복과 비교하면 기획의 중요성을 체감했다.
- 수익화와 성능을 같이 잡았다. 광고를 넣으면서 "앱이 느려졌다"는 인상을 주면 안 된다. 오히려 더 빨라진 앱에 광고가 자연스럽게 녹아든다.
- 테스트 플래그 하나로 개발/배포 전환.
kForceDisableAds덕에 개발 중에는 광고를 완전히 잊고 작업할 수 있었다.
아쉬운 점
- 영수증 검증이 아직 stub이다. Apple/Google 키를 발급받아야 실제 검증이 동작한다. 보안상 중요한 부분이라 배포 전에 반드시 구현해야 한다.
- 패스 만료 정보가 SharedPreferences에 있어서, 기기 시간을 조작하면 우회할 수 있다. 서버 시간 기반 검증을 추가하면 좋겠다.
배포 전 할 일
- AdMob 계정 등록 + 실제 광고 ID 발급
- App Store / Google Play 인앱 상품 등록
kForceDisableAds = false전환- 영수증 검증 실제 구현
- 실기기에서 광고 + 결제 플로우 테스트
💬 마무리
수익화는 "돈을 버는 기능"이 아니라 **"앱을 지속할 수 있게 하는 구조"**다.
무료 유저에게는 광고를 보여주되 절대 짜증나지 않게, 유료 유저에게는 ₩1,200에 7일간 깔끔한 경험을. 그 사이의 밸런스를 찾는 게 이번 작업의 핵심이었다.
성능 최적화는 덤이었는데, 결과적으로 광고보다 더 큰 UX 개선이었다. 불필요한 리빌드 15회를 1회로 줄이고, 탭 전환을 즉시로 바꾸고, DB 쿼리를 최소화한 건 — 유저가 "이 앱 빠르다"고 느끼게 해준다.
다음은 실제 배포. AdMob ID 받고, IAP 등록하고, 실기기 테스트 끝나면 스토어에 올린다. 🇹🇼