Chapter 06

연결
— 스토리에서
포토북으로

기능 하나가 완성됐습니다. 이제 그 기능을 기존 포토북 제작 플로우와 연결해야 합니다.
화면을 추가하고, 흐름을 설계하고, 예상치 못한 아키텍처 충돌을 마주하는 과정입니다.

이번 챕터 진행 흐름

상세 화면 추가 → 연결 시도 → 벽 발견 → 결정

스토리 목록이 완성된 뒤, 다음 단계는 사진을 보여주는 상세 화면과 포토북 제작으로의 연결이었습니다.

📸
✓ 완료
스토리 상세 화면 — PhotoStoryDetailFragment
선택된 스토리의 사진들을 3열 그리드로 표시하는 화면을 신규 구현했습니다. CoordinatorLayout + Toolbar + RecyclerView(spanCount=3) + FAB 구조로, "포토북 만들기" 버튼까지 포함합니다.
PhotoStoryDetailFragment RecyclerView 3열 그리드 FAB — 포토북 만들기
🔗
✓ 완료
네비게이션 흐름 연결
목록 → 상세 → 포토북 제작으로 이어지는 전체 흐름을 Contract, ViewModel, Fragment에 구현했습니다. activityViewModels로 스토리 공유, NavigateToDetail SideEffect로 화면 전환을 연결했습니다.
NavigateToDetail SideEffect activityViewModels SnapsSelectProductJunctionFactory
🐛
⚠ 문제 발견
FAB 미동작 — 아키텍처 충돌 발견
빌드는 성공했지만 실기기에서 FAB를 눌러도 아무 반응이 없었습니다. 원인을 파헤치자 근본적인 흐름 충돌이 드러났습니다. 기존 포토북 제작에는 서버가 부여한 "상품코드"가 필수인데, 나의스토리 진입로에는 이 과정이 없었습니다.
productCode 없음 기존 selfAI 흐름 충돌
🏗️
✓ 결정 완료
아키텍처 결정 — 웹팀 협업으로 전용 URL 방식
두 가지 선택지를 분석한 결과, 웹팀이 포토북 종류 선택 전용 페이지를 만들고 앱이 그 URL을 여는 방식을 선택했습니다. 사용자 경험이 가장 깔끔하며, 기존 포토북 제작 흐름을 그대로 재사용할 수 있습니다.
✓ 방향 결정 웹팀 협업 전용 포토북 선택 URL
스토리 상세 화면 구현

선택된 스토리의 사진을 한눈에 — 3열 그리드

스토리 목록에서 항목을 탭하면, 해당 스토리 안의 사진들을 그리드로 보여주는 상세 화면이 열립니다. Orbit MVI 패턴을 그대로 유지하며 신규 화면을 추가했습니다.

📋
PhotoStoryDetailFragment.kt
선택된 스토리 사진들을 3열 RecyclerView 그리드로 표시. CoordinatorLayout으로 스크롤 시 Toolbar 축소 효과 포함.
activityViewModels 공유 RecyclerView spanCount=3 FAB → onClickMakeBook()
🖼️
PhotoStoryDetailAdapter.kt
PhotoMetadata 리스트를 받아 정사각형 썸네일 그리드를 렌더링. Glide로 사진 로드.
ListAdapter + DiffUtil item: 1:1 비율 ImageView (ConstraintLayout dimensionRatio)
📐
fragment_photo_story_detail.xml
item_photo_story_detail.xml
CoordinatorLayout + AppBarLayout + RecyclerView + FAB. 아이템은 1:1 비율로 정사각형 커버 이미지.
CoordinatorLayout + AppBarLayout(CollapsingToolbar) + RecyclerView(spanCount=3) + FloatingActionButton
Kotlin PhotoStoryContract.kt — NavigateToDetail 추가
// SideEffect에 Detail 네비게이션 추가
sealed interface PhotoStorySideEffect {
    data class NavigateToDetail(
        val story: PhotoStory
    ) : PhotoStorySideEffect

    data class NavigateToBookMaking(
        val story: PhotoStory
    ) : PhotoStorySideEffect

    object RequestPermission : PhotoStorySideEffect
}

// ViewModel: 스토리 선택 시 selectedStory 저장 후 Detail로 이동
var selectedStory: PhotoStory? = null
fun onClickStory(story: PhotoStory) = intent {
    selectedStory = story
    postSideEffect(NavigateToDetail(story))
}
fun onClickMakeBook() = intent {
    selectedStory?.let {
        postSideEffect(NavigateToBookMaking(it))
    }
}
activityViewModels로 상태 공유
목록 Fragment와 상세 Fragment가 같은 Activity에 속하므로, activityViewModels()로 ViewModel을 공유했습니다. 목록에서 선택한 스토리를 상세 Fragment에서 별도 파라미터 없이 바로 참조합니다. 기존 앱 패턴을 그대로 따른 결과입니다.
FAB 미동작 — 예상치 못한 벽

빌드 성공, 그러나 버튼을 누르면 아무 일도 없다

빌드는 성공했습니다. 화면도 열렸습니다. 사진도 보였습니다. 그런데 "포토북 만들기" FAB를 탭하면 아무 반응이 없었습니다. 코드에는 문제가 없는데 왜일까?

증상
FAB 클릭 → 아무 반응 없음
ViewModel의 onClickMakeBook()이 호출되고, NavigateToBookMaking SideEffect가 발생하며, SnapsSelectProductJunctionFactory를 통해 포토북 편집 화면으로 이동하려 했지만 — 아무 일도 일어나지 않았습니다.
근본 원인
상품코드(productCode)가 없다
기존 selfAI 포토북 제작은 웹 홈 화면에서 상품코드를 받아오는 것이 전제입니다. 나의스토리는 홈을 거치지 않고 직접 진입하므로, 포토북 편집기에 넘겨야 할 productCode가 없습니다. 팩토리는 코드 없이는 동작하지 않습니다.
⚠ 아키텍처 충돌의 본질
기존 selfAI 흐름: 웹사이트 → 포토북 종류 선택(상품코드 발급) → 날짜·지역 정보 → 앱에서 해당 사진 조회 → 편집

나의스토리 흐름: 앱에서 사진 분류 → 스토리 선택 → (이 다음을 어떻게?)

두 흐름의 시작점이 반대입니다. 기존 흐름은 웹이 먼저 상품을 결정하고 앱에 사진을 가져오도록 지시하지만, 나의스토리는 앱이 먼저 사진을 분류하고 이후에 상품을 결정해야 합니다.
아키텍처 결정

두 가지 선택지 — AI가 분석하고, 사람이 결정했다

흐름 충돌을 해결하는 방법은 두 가지였습니다. Claude가 각 방식의 구현 방법과 트레이드오프를 분석했고, 최종 결정은 사람이 내렸습니다.

Option A
웹팀 전용 URL 방식
웹팀이 포토북 종류만 선택하는 전용 페이지를 만들고, 앱이 그 URL을 WebView로 엽니다. 사용자가 종류를 선택하면 상품코드가 발급되고, 기존 흐름이 그대로 이어집니다.
+ UX 흐름이 자연스러움
+ 기존 포토북 제작 코드 100% 재사용
+ 앱 코드 변경 최소화
Option B
홈 화면 전체 열기 방식
기존 홈 화면을 그대로 열어 selfAI 포토북 선택 플로우를 사용자가 직접 거치게 합니다. 스토리에서 선택한 사진은 나중에 병합하거나 무시합니다.
− UX가 어색함 (이미 분류된 사진이 있는데 다시 선택?)
− 스토리와 기존 플로우가 단절됨
− 빠른 구현이지만 사용자에게 불친절
★ 결정 이유
"앱이 이미 사진을 분류했는데 사용자가 다시 고르게 할 수 없다."
Option A를 선택했습니다. 웹팀과 협업해 포토북 종류 선택 전용 URL을 만들기로 했습니다. 앱은 스토리 FAB 클릭 시 해당 URL을 WebView로 열고, 사용자가 종류를 선택하면 상품코드를 받아 기존 편집 흐름에 진입합니다. 나의스토리에서 분류한 사진 리스트는 이 시점에 자동으로 주입됩니다.
결정된 최종 연결 흐름

앱 → 웹 → 앱 — 하이브리드 흐름의 완성형

나의스토리에서 포토북 편집기까지 이어지는 최종 흐름입니다. 앱과 웹이 협력해 사용자에게 끊김 없는 경험을 제공합니다.

1
홈 화면 — 나의스토리 진입 JS 주입
WebView 홈에 JS로 주입된 나의스토리 아이콘을 탭. JavascriptInterface를 통해 PhotoStoryActivity가 시작됩니다.
2
스토리 목록 MediaStore 클러스터링
기기 사진이 자동 분류되어 이벤트 단위 스토리 목록으로 표시됩니다. 날짜·장소 정보가 함께 보입니다.
3
스토리 상세 3열 그리드
선택한 스토리의 사진들을 그리드로 확인합니다. "포토북 만들기" FAB가 하단에 위치합니다.
4
포토북 종류 선택 웹팀 협업 · 전용 URL
FAB 탭 시 WebView로 포토북 종류 선택 전용 페이지를 엽니다. (웹팀과 협업 예정) 사용자가 포토북 사이즈/종류를 선택하면 서버에서 상품코드를 발급합니다.
5
포토북 편집기 기존 흐름 재사용
상품코드 + 스토리 사진 리스트를 SmartRecommendBookMakingActivity에 전달. 기존 AI 추천 포토북 편집 흐름이 그대로 시작됩니다.
AI가 분석한 핵심 포인트
기존 selfAI 플로우를 처음부터 재구현하는 것이 아니라 진입점만 바꾸는 방식입니다. 포토북 편집, AI 레이아웃 추천, 결제, 주문 — 이 모든 것은 기존 코드 그대로 사용합니다. 나의스토리는 "사용자가 직접 선택하지 않아도 되는 사진 묶음"을 자동으로 준비해주는 역할에만 집중합니다.
이 단계의 AI 활용 포인트

버그를 넘어 아키텍처까지 — AI가 분석한 것들

🔍
흐름 역추적
FAB 미동작의 진짜 원인 추적
단순 코드 버그가 아니라 productCode 부재라는 근본 원인을 파악하기 위해, 기존 selfAI 포토북 생성 흐름 전체를 역추적했습니다. 코드를 직접 읽으며 의존성 체인을 따라갔습니다.
⚖️
트레이드오프 분석
두 선택지의 구현 방법과 비용 비교
Option A와 B 각각에 대해 필요한 코드 변경 범위, 웹팀 협업 필요성, UX 품질을 구체적으로 분석했습니다. 단순 의견이 아니라 구현 관점의 비교를 제공했습니다.
🏗️
아키텍처 조언
기존 코드 최대 재사용 전략
포토북 편집, AI 추천, 결제 같은 복잡한 기존 기능을 건드리지 않고, 진입점만 교체하는 최소 변경 전략을 제안했습니다. 새로 짜는 것보다 기존 코드를 이해하는 것이 더 중요했습니다.
★ 이 단계의 핵심
코드 버그보다 흐름 충돌이 더 어렵다.
AI는 그 충돌을 언어로 설명해줬다.
FAB 하나가 안 눌렸을 뿐이지만, 그 뒤에는 앱과 웹이 각각 다른 시작점을 가진 두 흐름의 충돌이 있었습니다. 모바일 경험 없이도 AI와 함께 원인을 추적하고, 트레이드오프를 분석하고, 결정을 내릴 수 있었습니다. 다음은 웹팀과의 협업으로 완성하는 단계입니다.
🔗
이 단계의 산출물
스토리 상세 화면 완성 + 연결 아키텍처 결정
PhotoStoryDetailFragment(3열 그리드) 구현, 목록→상세→포토북 네비게이션 흐름 구성, FAB 미동작 원인(productCode 부재) 파악, 웹팀 협업으로 전용 URL 방식 결정까지 완료.
다음은 웹팀과 포토북 선택 전용 페이지 URL 연동 및 스토리 사진 자동 주입 구현입니다.
✓ 상세 화면 완성 ✓ 네비게이션 흐름 ✓ 연결 방식 결정 → 웹팀 협업 예정