grep

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에서 Compose 전환하기

2. 시작이 반, 마이그레이션 준비

[Step 1] 마이그레이션 대상 선정 및 전략 수립

저희 팀은 Compose 마이그레이션을 체계적으로 진행하기 위해 초기 마이그레이션 대상을 신중하게 선정하고, 단계적인 도입 전략을 수립했습니다. 핵심 목표는 마이그레이션의 위험 부담을 최소화하면서 팀원들이 Compose 개발 경험을 점진적으로 축적하는 것이었습니다.

1️⃣ 초기 마이그레이션 대상

기존 XML 구조에서 뷰와 로직이 비교적 명확하게 분리되어 있어 Compose로의 전환이 용이할 것으로 예상되는 화면들을 우선적으로 고려했습니다.

2️⃣ 점진적 도입 전략

앱 전체를 한 번에 Compose로 전환하는 대신, 기존의 XML 기반 UI와 Compose UI가 공존하는 하이브리드(Hybrid) 형태로 점진적인 마이그레이션을 진행하는 전략을 채택했습니다. 이를 통해 기존 기능의 안정성을 유지하면서 새로운 기술을 도입하고, 발생할 수 있는 문제에 유연하게 대처할 수 있도록 했습니다.

3️⃣ 안정성 확보를 위한 Firebase Remote Config 적용

새로운 기술 스택 도입에 따른 잠재적인 위험을 관리하고 안정성을 확보하기 위해, Firebase의 Remote Config를 활용했습니다. 이를 통해 만약 Compose 기반 UI에서 예상치 못한 문제가 발생할 경우, 언제든지 기존의 XML 방식으로 동작하도록 원격에서 제어할 수 있는 안전장치를 마련했습니다.

[Step 2] Compose 학습 리소스 및 팀 스터디 진행

🤔 문제 인식

마이그레이션을 진행하면서 팀원들 각자가 보유한 Compose 개발에 대한 이해도와 숙련도가 상이하여, 작성되는 코드의 일관성이 부족하고, 표준에서 많이 벗어나는 등의 어려움에 직면했습니다. 이러한 상황은 장기적으로 코드 유지보수성을 저해하고 팀 협업 효율성을 떨어뜨릴 수 있다고 판단했습니다.

🎯 스터디 목표

이러한 문제점을 해결하고 팀 전체의 Compose 개발 역량을 상향 평준화하며, 통일된 코딩 규칙을 확립하기 위해 체계적인 스터디를 시작했습니다. Compose의 기본적인 사용법뿐만 아니라, 내부 동작 원리에 대한 깊이 있는 이해를 통해 더욱 효율적이고 안정적인 코드를 작성할 수 있도록 하는 데 스터디의 주요 목표를 두었습니다.

📚 활용 리소스

[Step 3] 개발 환경 설정

build.gradle에 의존성 추가

3. Compose 속으로, 개발자의 성장통 💡

저희 팀은 “Jetpack Compose Internals” 도서와 “Android 공식 문서”를 중심으로 심도 깊은 스터디를 진행하며, Compose의 핵심 내부 동작 원리를 상세히 이해하는 데 주력했습니다. 이 과정을 통해 단순한 사용법을 넘어, Compose의 설계 철학을 깊이 이해하고 더욱 효율적이고 유지보수가 용이한 코드를 작성할 수 있는 기반을 마련했습니다.

Jetpack Compose Internals 도서

4. 어디부터, 어떻게 바꿔나갔을까?

초기 마이그레이션 대상 선정 상세화

저희 팀은 초기 Compose 마이그레이션의 성공적인 안착을 위해 신중하게 대상 화면을 선정했습니다. 기존 XML 기반 코드의 특성과 Compose로의 전환 용이성을 종합적으로 고려하여, 다음과 같은 화면들을 우선적인 마이그레이션 대상으로 결정하고 진행했습니다.

✅ 초기 마이그레이션 대상 선정 시 고려사항

점진적 도입 전략 (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로 명확히 분리하여 전달하는 구조로 전환했습니다.

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 원리에 대한 이해 부족에서 비롯되었습니다.

[해결]

코딩 컨벤션 및 팀 표준 정립 과정 ✍️

[문제]

팀원들마다 Composable 함수 명명, Modifier 사용 방식, State 관리 패턴 등이 달라 코드의 일관성이 저해되고 가독성이 떨어지는 문제가 있었습니다. 이는 협업 과정에서 불필요한 논쟁을 유발하고 코드 리뷰에 어려움을 주었습니다.

[해결]

API Guidelines for Jetpack Compose를 기반으로 자체 Compose Coding 가이드 문서를 작성했습니다. 이 가이드는 다음과 같은 핵심 원칙을 포함했습니다.

6. 마침표가 아닌 다음 챕터를 향해 (Conclusion)

이 이미지는 생성형 AI를 활용해 제작하였습니다.

Jetpack Compose로의 마이그레이션은 단순히 기술 스택을 변경하는 것을 넘어, 저희 팀의 Android UI 개발 패러다임과 아키텍처 사고방식에 큰 변화를 가져온 여정이었습니다. 이 과정에서 새로운 기술의 강력함과 유연성을 직접 경험하며 개발 생산성과 코드 품질 향상이라는 목표에 한 걸음 더 다가설 수 있었습니다. 🚀

물론, 새로운 기술을 도입하는 과정에는 예상치 못한 어려움과 도전이 따르기 마련입니다. ViewModel 주입 방식의 혼란, Hilt와의 통합 문제, 그리고 Recomposition 최적화와 같은 난관에 부딪히기도 했습니다. 하지만 저희 팀은 이러한 문제들을 회피하지 않고, 심층적인 스터디, 활발한 팀 내 논의, 그리고 체계적인 코딩 가이드 정립을 통해 하나씩 해결해 나갔습니다. 특히 Firebase Remote Config를 통한 안정성 확보 전략은 신기술 도입의 위험 부담을 크게 줄여주었습니다. 🛡️

아직 NOL Android 앱의 모든 UI가 Compose로 전환된 것은 아니지만, 초기 마이그레이션의 성공적인 경험은 앞으로의 전환 작업에 큰 자신감을 불어넣어 주었습니다. Compose는 단순한 UI 툴킷을 넘어, 반응형 프로그래밍, 단방향 데이터 흐름, 그리고 효율적인 상태 관리와 같은 현대적인 개발 원칙을 자연스럽게 체화할 수 있도록 이끌어주었습니다. 🌱

이 글이 Compose 도입을 고민하거나 마이그레이션을 진행 중인 다른 개발팀에게 작은 영감이 되기를 바랍니다. ✨

Compose 마이그레이션을 위한 핵심 체크리스트 및 팁 💡

Jetpack Compose 도입을 고민하거나 마이그레이션 프로젝트를 시작하려는 팀을 위해, 저희가 경험을 통해 얻은 핵심 체크리스트와 팁을 공유합니다.

다음 편 예고: 개발 과정에서 만난 도전들 🛠️

다음 편에서는 이번 마이그레이션 프로젝트에서 실제 Compose를 앱에 적용하며 겪었던 구체적인 개발 과정과, 코드의 완성도를 높이고 성능을 최적화하기 위해 마주했던 다양한 도전에 대한 내용을 2편에 나누어 상세히 다룰 예정입니다. 더 깊이 있는 이야기와 실질적인 노하우를 기대해주세요!

<참고 자료 / 문서>

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

지금 채용 중인 포지션 보러가기 >