Engineering
낡은 캔버스에서 새 캔버스로: Jetpack Compose와 함께한 마이그레이션 여정
2025년 8월 22일
원문에서 보기 ↗[Jetpack Compose — Part 1] 낡은 캔버스에서 새 캔버스로: Jetpack Compose와 함께한 마이그레이션 여정

이 이미지는 생성형 AI를 활용해 제작하였습니다.
익숙했던 XML 캔버스를 뒤로하고, Jetpack Compose라는 새로운 캔버스로 Android 앱의 UI를 그려나가기 위한 저희 팀의 마이그레이션 여정을 공유하고자 합니다. 단순히 기술을 도입하는 것을 넘어, 그 과정에서 겪었던 고민과 도전, 그리고 학습을 통한 성장의 이야기를 담아보았습니다. 🌱
Compose는 2021년 7월 안드로이드에서 출시한 Jetpack 라이브러리 중 하나입니다. Compose는 composable 함수를 사용하여 Android UI를 보다 빠르고 강력하게 구현할 수 있으며 유지보수에 용이하다는 장점을 가지고 있습니다. 저희는 서비스 중인 NOL Android 앱의 구조 변경 및 리빌딩을 천천히 준비하고 있는 참이어서 자연스럽게 Compose 도입 여부에 대해 고민하게 되었고, 먼저 사용해보기 위해 선행적인 작업들을 시작하게 되었습니다. 🤔
1. 그래서 Compose가 무엇인데?
Compose란 무엇인가?
Jetpack Compose는 Android UI 개발의 현대적인 혁신을 이끄는 선언형 UI 툴킷입니다. 기존의 명령형(imperative) View 시스템과는 근본적으로 다른 패러다임을 제시하며, UI를 코틀린(Kotlin) 언어로 직접 기술함으로써 개발 생산성을 획기적으로 향상시킵니다. 데이터가 변경됨에 따라 UI가 자동으로 업데이트되는 반응형(reactive) 프로그래밍 모델을 채택하여, 복잡한 UI 상태 관리를 보다 직관적이고 효율적으로 만들어줍니다.
왜 Compose를 사용해야 했는가? (마이그레이션 추진 배경)
저희 팀은 기존 XML 기반 앱의 UI 개발 과정에서 발생하는 여러 가지 고충과 한계를 극복하고, 보다 미래 지향적인 개발 환경을 구축하고자 Jetpack Compose로의 마이그레이션을 적극적으로 추진하게 되었습니다.
- 개발 생산성 향상: XML 레이아웃 작성, View 바인딩 설정, RecyclerView의 Adapter 구현 등 반복적이고 장황한 코드(boilerplate code) 작성을 최소화하고, 간결하고 직관적인 코틀린 코드를 통해 UI를 빠르게 개발하고 유지보수할 수 있게 되었습니다.
- UI 코드의 간결화 및 일관성: XML과 코틀린 코드를 오가며 UI를 개발하는 방식에서 벗어나, 단일 언어(코틀린)로 UI의 구조, 스타일, 동작을 모두 정의함으로써 코드베이스의 일관성을 높이고, 전반적인 코드 이해도를 향상시킬 수 있었습니다.
- 개발자 경험 개선 (Developer Experience): Android Studio의 강력한 실시간 미리보기(Live Preview) 기능, 코드 변경에 따른 즉각적인 UI 업데이트(Hot Reload) 등을 통해 UI를 시각적으로 확인하면서 개발하는 즐거움을 경험하고, UI 디자인 및 구현 과정을 더욱 효율적으로 진행할 수 있었습니다.
- 성능 최적화 잠재력: Compose는 UI를 재구성(Recomposition)하는 과정에서 변경된 부분만을 선택적으로 업데이트하는 스마트한 메커니즘을 내장하고 있어, 불필요한 렌더링을 최소화하고 앱의 전반적인 성능을 향상시킬 수 있는 잠재력을 가지고 있다고 판단했습니다.
- 기술 부채 감소 및 최신 기술 도입: 오랜 기간 유지보수되어 온 XML 기반 코드의 복잡성을 줄이고, Android 개발의 최신 기술 트렌드를 적극적으로 수용함으로써 앱의 지속 가능성을 높이고, 개발팀의 기술 역량을 강화하고자 했습니다.

XML에서 Compose 전환하기
2. 시작이 반, 마이그레이션 준비
[Step 1] 마이그레이션 대상 선정 및 전략 수립
저희 팀은 Compose 마이그레이션을 체계적으로 진행하기 위해 초기 마이그레이션 대상을 신중하게 선정하고, 단계적인 도입 전략을 수립했습니다. 핵심 목표는 마이그레이션의 위험 부담을 최소화하면서 팀원들이 Compose 개발 경험을 점진적으로 축적하는 것이었습니다.
1️⃣ 초기 마이그레이션 대상
기존 XML 구조에서 뷰와 로직이 비교적 명확하게 분리되어 있어 Compose로의 전환이 용이할 것으로 예상되는 화면들을 우선적으로 고려했습니다.
- 단일 페이지로 구성되어 다른 화면과의 의존성이 낮고, 변경으로 인한 영향 범위가 제한적인 화면 (예: 사용자 로그인 화면, 앱 설정 화면 및 각 세부 설정 화면 등)을 초기 마이그레이션 대상으로 확정했습니다. 이러한 화면들은 Compose의 기본적인 컴포넌트 및 레이아웃 기능을 학습하고 적용하는 데 좋은 시험대가 될 수 있다고 판단했습니다.
- 홈/서브홈 화면의 경우, 단순히 뷰와 로직 분리 여부뿐만 아니라, 당시 진행 중이던 페이지 개편 일정과 Compose 도입 시점을 맞추어 시너지 효과를 창출하고자 했습니다.
2️⃣ 점진적 도입 전략
앱 전체를 한 번에 Compose로 전환하는 대신, 기존의 XML 기반 UI와 Compose UI가 공존하는 하이브리드(Hybrid) 형태로 점진적인 마이그레이션을 진행하는 전략을 채택했습니다. 이를 통해 기존 기능의 안정성을 유지하면서 새로운 기술을 도입하고, 발생할 수 있는 문제에 유연하게 대처할 수 있도록 했습니다.
3️⃣ 안정성 확보를 위한 Firebase Remote Config 적용
새로운 기술 스택 도입에 따른 잠재적인 위험을 관리하고 안정성을 확보하기 위해, Firebase의 Remote Config를 활용했습니다. 이를 통해 만약 Compose 기반 UI에서 예상치 못한 문제가 발생할 경우, 언제든지 기존의 XML 방식으로 동작하도록 원격에서 제어할 수 있는 안전장치를 마련했습니다.
[Step 2] Compose 학습 리소스 및 팀 스터디 진행
🤔 문제 인식
마이그레이션을 진행하면서 팀원들 각자가 보유한 Compose 개발에 대한 이해도와 숙련도가 상이하여, 작성되는 코드의 일관성이 부족하고, 표준에서 많이 벗어나는 등의 어려움에 직면했습니다. 이러한 상황은 장기적으로 코드 유지보수성을 저해하고 팀 협업 효율성을 떨어뜨릴 수 있다고 판단했습니다.
🎯 스터디 목표
이러한 문제점을 해결하고 팀 전체의 Compose 개발 역량을 상향 평준화하며, 통일된 코딩 규칙을 확립하기 위해 체계적인 스터디를 시작했습니다. Compose의 기본적인 사용법뿐만 아니라, 내부 동작 원리에 대한 깊이 있는 이해를 통해 더욱 효율적이고 안정적인 코드를 작성할 수 있도록 하는 데 스터디의 주요 목표를 두었습니다.
📚 활용 리소스
- Google 공식 문서 및 Code Labs: Jetpack Compose의 기본 개념, UI 구성 요소(Components), 레이아웃 방식, 상태 관리 기법 등을 학습하기 위한 필수적인 공식 자료를 적극적으로 활용했습니다.
- “Jetpack Compose Internals” 스터디 (심층 학습): Compose Runtime, Compose Compiler, Composable 함수의 내부 동작 메커니즘을 상세하게 다루는 전문 서적을 선정하여 팀 스터디를 진행했습니다. 이를 통해 Compose의 핵심 원리인 Recomposition 메커니즘, 슬롯 테이블(Slot Table)과 변경 목록(List of Changes)의 역할, Composer의 동작 방식, 그리고 상태 스냅샷 시스템(State snapshot system)을 통한 동시성 제어 방식(MVCC) 등에 대한 깊이 있는 이해를 도모했습니다.
- cs.android.com (Android Open Source Project): 필요에 따라 Android 오픈소스 프로젝트 내 Compose 프레임워크의 실제 소스 코드를 직접 탐색하며, 스터디 과정에서 논의된 이론적인 내용들을 검증하고 더욱 깊이 있는 이해를 얻기 위해 활용했습니다.
[Step 3] 개발 환경 설정
- Android Studio 및 Kotlin 버전: 안정적인 Compose 개발 환경을 구축하기 위해 Compose 사용에 권장되는 최신 안정화 버전의 Android Studio와 Kotlin 컴파일러를 사용하도록 팀 전체 개발 환경을 통일했습니다. Compose Compiler는 특정 Kotlin 버전에 의존성을 가지므로, 프로젝트의 Kotlin 버전을 신중하게 관리했습니다.
- Gradle 설정: 기존 프로젝트에서 Compose 기능을 활성화하고 필요한 Compose 라이브러리들을 프로젝트 의존성에 추가하기 위해 build.gradle 파일을 적절하게 설정했습니다. 점진적인 마이그레이션을 고려하여 필요한 Compose 라이브러리들을 유연하게 관리할 수 있도록 설정했습니다.

build.gradle에 의존성 추가
3. Compose 속으로, 개발자의 성장통 💡
저희 팀은 “Jetpack Compose Internals” 도서와 “Android 공식 문서”를 중심으로 심도 깊은 스터디를 진행하며, Compose의 핵심 내부 동작 원리를 상세히 이해하는 데 주력했습니다. 이 과정을 통해 단순한 사용법을 넘어, Compose의 설계 철학을 깊이 이해하고 더욱 효율적이고 유지보수가 용이한 코드를 작성할 수 있는 기반을 마련했습니다.
- Composable 함수의 본질과 속성 @Composable 어노테이션이 붙은 함수는 단순히 UI를 그리는 역할을 하는 것이 아니라, Compose 런타임에 의해 특별하게 관리되는 단위입니다. 저희는 @Composable 함수가 UI 트리에 노드를 방출(emit)하는 일련의 과정을 이해하고, 멱등성(Idempotent), 재시작 가능성(Restartable), 통제되지 않은 사이드 이펙트 방지와 같은 중요한 속성들을 학습했습니다. 특히, UI 업데이트의 효율성을 극대화하기 위해 Compose가 내부적으로 활용하는 위치 기억법(Positional Memoization) 과 동적 UI 생성을 위한 key 파라미터의 올바른 사용법을 숙지하는 데 힘썼습니다.
- Compose 컴파일러의 역할 Compose 컴파일러는 Kotlin 컴파일러 플러그인으로, 개발자가 작성한 Compose 코드를 Android 런타임에서 효율적으로 실행될 수 있도록 변환하는 핵심적인 역할을 수행합니다. 저희는 컴파일러가 @Composable 함수에 Composer 파라미터를 암묵적으로 주입하는 과정, 람다식 메모이제이션(Lambda Memoization)을 통한 불필요한 객체 생성을 방지하는 최적화, 그리고 @Immutable 및 @Stable 어노테이션을 활용하여 컴파일러에게 코드의 안정성 정보를 제공하고 스마트 Recomposition을 가능하게 하는 메커니즘 등을 자세히 학습했습니다.
- Compose 런타임의 동작 메커니즘 Compose 런타임은 컴파일러에 의해 변환된 코드를 실제로 실행하고 UI를 관리하는 역할을 담당합니다. 저희는 런타임의 핵심 데이터 구조인 슬롯 테이블(Slot Table)이 Composition의 현재 상태를 효율적으로 저장하는 방식, Composer가 Composable 함수에서 방출된 UI 정보를 슬롯 테이블에 기록하고 변경 사항을 관리하는 과정, 그리고 Applier 를 통해 슬롯 테이블에 기록된 변경 사항들이 실제 Android View 트리에 반영되는 과정을 상세히 이해했습니다. 또한, Recomposer 가 State의 변경을 감지하고 UI 업데이트(Recomposition)를 트리거하는 역할과 전반적인 UI 생명주기를 관리하는 방식을 학습했습니다.
- 상태 스냅샷 시스템 (State Snapshot System) Compose는 복잡한 UI 상태를 효율적이고 안전하게 관리하기 위해 상태 스냅샷 시스템이라는 강력한 메커니즘을 제공합니다. 저희는 이 시스템이 MVCC(Multiversion Concurrency Control) 와 유사한 원리를 기반으로 동작하며, Snapshot 이라는 불변의 상태 사본을 통해 여러 스레드에서 안전하게 상태를 읽고 쓸 수 있도록 지원하는 방식을 학습했습니다. 또한, StateObject 와 StateRecord 를 통해 상태 변화를 추적하고 Recomposition을 효율적으로 관리하는 내부 구조에 대해서도 학습했습니다. readObserver와 writeObserver를 통해 상태 변화를 감지하고 UI를 업데이트하는 흐름을 이해했습니다.
- Effect Handler의 활용 Composable 함수 내에서 네트워크 요청, 데이터베이스 접근, 타이머 설정 등과 같은 사이드 이펙트(Side Effects) 를 안전하고 효율적으로 처리하기 위해 Effect Handler의 중요성을 인식하고, LaunchedEffect, DisposableEffect, SideEffect와 같은 다양한 Effect Handler의 사용법과 각각의 특징을 학습했습니다. 이를 통해 Composable 함수의 생명주기에 맞춰 사이드 이펙트를 관리하고, 리소스 누수나 예기치 않은 동작을 방지하는 방법을 익혔습니다.

Jetpack Compose Internals 도서
4. 어디부터, 어떻게 바꿔나갔을까?
초기 마이그레이션 대상 선정 상세화
저희 팀은 초기 Compose 마이그레이션의 성공적인 안착을 위해 신중하게 대상 화면을 선정했습니다. 기존 XML 기반 코드의 특성과 Compose로의 전환 용이성을 종합적으로 고려하여, 다음과 같은 화면들을 우선적인 마이그레이션 대상으로 결정하고 진행했습니다.
- 로그인 화면: 비교적 UI 구조가 단순하고, 복잡한 비즈니스 로직이나 다른 화면과의 의존성이 낮아 Compose의 기본적인 UI 컴포넌트(TextField, Button 등)를 학습하고 적용하는 데 매우 적합했습니다.
- 설정 및 각 설정의 상세 화면: 여러 개의 독립적인 단일 페이지로 구성되어 있어, 각 화면을 개별적으로 Compose로 전환하는 것이 용이했습니다. LazyColumn이나 Scaffold와 같은 기본적인 Compose 레이아웃 컴포넌트의 활용 경험을 쌓는 데 도움이 되었습니다.
- 홈/서브홈 화면: 이 페이지들의 경우, 기존 코드의 뷰와 로직 분리 상황보다는 당시 진행 중이던 페이지 개편 일정과 Compose 도입 시점을 전략적으로 연계하여 마이그레이션을 진행하게 되었습니다. 새로운 UI/UX 디자인에 맞춰 Compose의 최신 기능들을 적극적으로 활용할 수 있는 기회라고 판단했습니다.
✅ 초기 마이그레이션 대상 선정 시 고려사항
- 낮은 복잡도: 초기 단계에는 기능적 복잡성이 낮고 UI 구현이 비교적 단순한 화면을 우선적으로 선정하여 Compose에 대한 이해도를 높이고 발생 가능한 기술적인 어려움을 최소화하고자 했습니다.
- 독립성: 다른 화면이나 앱의 핵심 기능에 미치는 영향이 적거나 제한적인 화면을 대상으로 마이그레이션을 진행하여, 잠재적인 오류 발생 시 그 영향 범위를 최소화하고자 했습니다.
- 명확한 뷰-로직 분리: 기존 XML 코드에서 UI 로직이 ViewModel 등을 통해 비교적 명확하게 분리되어 있는 화면들을 우선적으로 선정하여 Compose로의 데이터 흐름 관리 패턴 전환을 용이하게 하고자 했습니다.
- 빠른 피드백 가능성: Compose 적용 후 UI 변경 사항을 빠르게 확인하고 검증할 수 있는 화면들을 우선적으로 선정하여 개발 효율성을 높이고, 팀원들의 자신감을 얻고자 했습니다.
점진적 도입 전략 (Hybrid Development)
저희는 앱 전체를 한 번에 Compose로 재작성하는 대신, 기존의 XML 기반 UI와 새로운 Compose UI가 공존하는 하이브리드(Hybrid) 형태로 점진적인 마이그레이션을 추진했습니다. 이 전략은 기존 앱의 안정성을 유지하면서 새로운 기술을 도입할 수 있는 효과적인 방법이라고 판단했습니다.
⚫ ComposeView 활용
기존 XML 레이아웃 내부에 ComposeView를 추가하여 특정 UI 컴포넌트나 작은 화면 영역을 Compose로 구현했습니다. 이를 통해 기존의 Activity나 Fragment의 생명주기와 Compose UI의 생명주기를 통합하고, 점진적으로 Compose 영역을 확장해 나갈 수 있었습니다.
⚫ AndroidView 활용
반대로 Compose UI 내부에 기존의 복잡한 커스텀 View나 WebView와 같이 아직 Compose로 완전히 대체하기 어려운 Android View들을 AndroidView 컴포저블을 사용하여 임베딩했습니다. 이를 통해 기존 기능의 호환성을 유지하면서 Compose의 장점을 활용할 수 있었습니다.
⚫ ViewModel과의 통합 및 데이터 흐름 변경
기존 앱은 LiveData를 기반으로 데이터 옵저빙을 처리하는 경우가 많았습니다. Compose의 단방향 데이터 흐름(Unidirectional Data Flow) 및 MVI(Model-View-Intent) 아키텍처 패턴과의 시너지를 극대화하기 위해, ViewModel에서 UI 상태를 ViewState로, UI에서 발생하는 일회성 액션을 Event로 명확히 분리하여 전달하는 구조로 전환했습니다.
- ViewState: UI의 모든 상태를 표현하는 불변(immutable) 데이터 클래스로 정의했습니다. ViewModel은 비즈니스 로직 처리 후 업데이트된 ViewState를 StateFlow나 SharedFlow를 통해 발행하고, Compose UI는 이를 collectAsState() 함수로 구독하여 UI를 자동으로 업데이트했습니다.
- Event: 사용자 입력이나 특정 비즈니스 로직 완료 등 UI에서 한 번만 처리되어야 하는 일회성 액션(예: SnackBar 메시지 표시, 화면 전환, 토스트 메시지 등)을 Event로 정의했습니다. ViewModel은 Event를 SharedFlow로 발행하고, Compose UI에서는 LaunchedEffect를 사용하여 해당 Event를 소비하고 처리했습니다. 이를 통해 Event가 Recomposition으로 인해 반복 처리되거나 상태로 남아있어 문제가 발생하는 것을 방지했습니다.
- 이러한 ViewState와 Event 분리 전략은 ViewModel과 Composable 간의 책임 분리를 명확히 하고, UI 상태를 더욱 예측 가능하게 만들었으며, 테스트 용이성을 크게 향상시켰습니다.

MVI 패턴의 단반향 순환 구조
5. 넘어져도 괜찮아, 마이그레이션 중 만난 문제들 🚧
Compose 마이그레이션 과정은 순탄하지만은 않았고, 여러 가지 기술적인 어려움에 직면하기도 했습니다. 하지만 팀원들과의 적극적인 스터디와 협력을 통해 각 문제점들을 분석하고 해결해 나갈 수 있었습니다.
ViewModel 주입 방식에 대한 이해 부족 😵💫
[문제]
팀원들이 ViewModel을 Composable 함수 내에서 직접 생성하는 경우가 많았습니다. 이는 ViewModel의 생명주기와 Composable의 Recomposition 사이의 불일치를 야기하고, 테스트 용이성을 저해하는 원인이 되었습니다.
[해결]
ViewModel의 올바른 생명주기 관리의 중요성을 강조하고, ViewModel은 Composable이 아닌 Activity 또는 Fragment 수준에서 생성하여 상태 호이스팅(State Hoisting) 패턴을 통해 필요한 Composable 함수에 파라미터로 전달하는 방식을 표준으로 채택했습니다. Android Jetpack의 androidx.lifecycle:lifecycle-viewmodel-compose 라이브러리의 viewModel() 함수를 사용하여 Composable 내부에서 ViewModel을 안전하게 가져오는 방법을 팀 전체에 공유하고 교육했습니다.
Hilt와의 통합 어려움 💥
[문제]
기존에 도입되어 있던 DI(Dependency Injection) 라이브러리인 Hilt와 Compose를 함께 사용하는 부분에서 많은 어려움을 겪었습니다. 특히 ViewModel과 Composable 함수에 의존성을 주입하는 방식에 대한 혼란이 있었습니다.
[해결]
Hilt의 @HiltViewModel 어노테이션을 사용하여 ViewModel을 정의하고, @AndroidEntryPoint 어노테이션이 붙은 Activity 또는 Fragment에서 hiltViewModel() 컴포저블 함수를 통해 ViewModel 인스턴스를 가져오는 방식을 채택했습니다. 또한, Composable 함수에 직접 의존성을 주입해야 할 경우에는 @Inject 어노테이션과 함께 @Composable 주석이 붙은 팩토리 함수를 사용하거나, 필요에 따라 CompositionLocal을 활용하는 방안을 스터디하여 적용했습니다.
Recomposition의 과도한 발생 💨
[문제]
초기 Compose 코드를 작성하면서 불필요한 Recomposition이 과도하게 발생하여 앱 성능 저하로 이어지는 경우가 많았습니다. 이는 Composable 함수의 멱등성 과 Recomposition 원리에 대한 이해 부족에서 비롯되었습니다.
[해결]
- remember 의 올바른 사용법을 재연구했습니다. 특히 remember가 슬롯 테이블 에 값을 캐싱하여 Recomposition 시 재사용 가능하게 하는 메커니즘을 이해하고, 비용이 큰 연산 결과나 객체를 remember로 감싸는 습관을 들였습니다.
- derivedStateOf를 사용하여 여러 State 간의 의존성을 효율적으로 관리하고, 파생된 State의 값이 실제로 변경될 때만 Recomposition이 발생하도록 최적화했습니다.
- LazyColumn이나 LazyRow와 같은 Lazy 컴포넌트에서 key 파라미터의 중요성을 강조하고, 리스트 아이템의 고유 ID를 key로 지정하여 아이템의 추가/삭제/이동 시 불필요한 Recomposition을 최소화했습니다.
코딩 컨벤션 및 팀 표준 정립 과정 ✍️
[문제]
팀원들마다 Composable 함수 명명, Modifier 사용 방식, State 관리 패턴 등이 달라 코드의 일관성이 저해되고 가독성이 떨어지는 문제가 있었습니다. 이는 협업 과정에서 불필요한 논쟁을 유발하고 코드 리뷰에 어려움을 주었습니다.
[해결]
API Guidelines for Jetpack Compose를 기반으로 자체 Compose Coding 가이드 문서를 작성했습니다. 이 가이드는 다음과 같은 핵심 원칙을 포함했습니다.
- @Composable 함수의 명명 규칙: PascalCase 명사로 명명하여 선언적 엔티티로 인식하도록 했습니다. 값을 반환하는 Composable 함수에는 remember 접두사를 붙여 재구성 동안 객체가 유지됨을 명시적으로 알리도록 했습니다.
- Emit 또는 값 반환 (둘 중 하나만): Composable 함수는 UI 콘텐츠를 생성(emit)하거나 값을 반환해야 하며, 둘 다 동시에 수행해서는 안 된다는 원칙을 따랐습니다. 상태 호이스팅 패턴을 통해 매개변수 로 상태를 전달하고 콜백 으로 이벤트를 처리하도록 유도했습니다.
- Modifier 매개변수 규칙: 모든 Element 함수는 modifier: Modifier = Modifier 형태로 Modifier 매개변수를 첫 번째 선택적 매개변수로 받도록 강제했습니다. 또한, Modifier는 항상 then() 을 통해 기존 체인 뒤쪽에 연결하도록 했습니다. 이는 Modifier의 순서 의존성 과 재사용성 을 고려한 것이었습니다.
- Layout 컴포저블의 content 매개변수 규칙: Layout 함수가 자식 UI를 배치하는 역할을 수행할 때, 가장 주요한 Composable 함수 매개변수를 content로 명명하고 Trailing Lambda 문법을 활용할 수 있도록 매개변수 목록의 마지막에 배치했습니다.
- Stateless 와 Controlled @Composable 함수 사용 권장: Composable 함수가 자체 상태를 가지지 않고 외부에서 상태를 받아 UI를 구성하는 Stateless 방식을 채택하도록 했습니다. 상태를 가지는 Composable 함수의 경우에도 외부에서 상태를 제어할 수 있도록 Controlled Composable 형태로 설계하도록 가이드했습니다. 이는 컴포넌트의 재사용성과 테스트 용이성을 높이기 위함입니다.
- 상태(State) 와 이벤트(Event) 분리: 상태는 현재의 값으로 관리하고, 이벤트는 상태를 변경하는 트리거 역할만 하도록 명확히 분리했습니다. Composable 함수는 이벤트가 아닌 상태를 기반으로 UI를 그리도록 했습니다.
- Hoisted State Types 활용: 여러 개의 상태 및 콜백 을 하나의 인터페이스 나 클래스 로 그룹화하는 Hoisted State Types 패턴을 도입하여 Composable 함수의 매개변수 목록을 단순화하고, 상태 간의 일관성을 유지하도록 했습니다. Hoisted State Types는 반드시 @Stable로 선언된 인터페이스여야 하며, 팩토리 함수 를 통해 기본 구현을 제공하도록 했습니다.
6. 마침표가 아닌 다음 챕터를 향해 (Conclusion)

이 이미지는 생성형 AI를 활용해 제작하였습니다.
Jetpack Compose로의 마이그레이션은 단순히 기술 스택을 변경하는 것을 넘어, 저희 팀의 Android UI 개발 패러다임과 아키텍처 사고방식에 큰 변화를 가져온 여정이었습니다. 이 과정에서 새로운 기술의 강력함과 유연성을 직접 경험하며 개발 생산성과 코드 품질 향상이라는 목표에 한 걸음 더 다가설 수 있었습니다. 🚀
물론, 새로운 기술을 도입하는 과정에는 예상치 못한 어려움과 도전이 따르기 마련입니다. ViewModel 주입 방식의 혼란, Hilt와의 통합 문제, 그리고 Recomposition 최적화와 같은 난관에 부딪히기도 했습니다. 하지만 저희 팀은 이러한 문제들을 회피하지 않고, 심층적인 스터디, 활발한 팀 내 논의, 그리고 체계적인 코딩 가이드 정립을 통해 하나씩 해결해 나갔습니다. 특히 Firebase Remote Config를 통한 안정성 확보 전략은 신기술 도입의 위험 부담을 크게 줄여주었습니다. 🛡️
아직 NOL Android 앱의 모든 UI가 Compose로 전환된 것은 아니지만, 초기 마이그레이션의 성공적인 경험은 앞으로의 전환 작업에 큰 자신감을 불어넣어 주었습니다. Compose는 단순한 UI 툴킷을 넘어, 반응형 프로그래밍, 단방향 데이터 흐름, 그리고 효율적인 상태 관리와 같은 현대적인 개발 원칙을 자연스럽게 체화할 수 있도록 이끌어주었습니다. 🌱
이 글이 Compose 도입을 고민하거나 마이그레이션을 진행 중인 다른 개발팀에게 작은 영감이 되기를 바랍니다. ✨
Compose 마이그레이션을 위한 핵심 체크리스트 및 팁 💡
Jetpack Compose 도입을 고민하거나 마이그레이션 프로젝트를 시작하려는 팀을 위해, 저희가 경험을 통해 얻은 핵심 체크리스트와 팁을 공유합니다.
- 마이그레이션 대상 선정 : 모든 화면을 한 번에 바꾸려 하지 마세요. 독립적이고 UI가 단순한 화면부터 시작하여 팀의 경험을 쌓는 것이 중요합니다.
- 안정성 확보: Firebase Remote Config와 같은 도구를 활용해 신기술 도입에 따른 위험을 관리하세요. 문제 발생 시 언제든지 기존 UI로 전환할 수 있는 안전장치를 마련하는 것이 필수입니다.
- 팀원 간 기술 격차 해소 : 팀원 모두가 Compose의 핵심 원리(Recomposition, 상태 관리 등)를 깊이 이해하도록 스터디와 논의를 진행하세요. 일관성 있는 코드 품질을 위한 첫걸음입니다.
- 코딩 가이드 정립 : 팀만의 Compose Coding 가이드를 만들고 공유하세요. 일관성 있는 코딩 스타일은 협업 효율을 높이고 코드 품질을 향상시킵니다.
다음 편 예고: 개발 과정에서 만난 도전들 🛠️
다음 편에서는 이번 마이그레이션 프로젝트에서 실제 Compose를 앱에 적용하며 겪었던 구체적인 개발 과정과, 코드의 완성도를 높이고 성능을 최적화하기 위해 마주했던 다양한 도전에 대한 내용을 2편에 나누어 상세히 다룰 예정입니다. 더 깊이 있는 이야기와 실질적인 노하우를 기대해주세요!

<참고 자료 / 문서>
- API Guidelines for Jetpack Compose: https://developer.android.com/jetpack/androidx/releases/compose?hl=ko
- Jetpack Compose Internals:https://leanpub.com/composeinternalskor
- Style guidelines for Jetpack Compose APIs | Android Developers: https://developer.android.com/develop/ui/compose/api-guidelines?hl=ko
- Coding conventions | Kotlin Documentation:https://kotlinlang.org/docs/coding-conventions.html

누구나 마음 편히 놀 수 있는 세상을 함께 만들어갈 동료를 찾고 있어요 :)