Droidknights 2023 - 이상훈 - 기존 앱을 Jetpack Compose로 마이그레이션 하기
기존 안드로이드 뷰 시스템 (XML, RecyclerView 등) 으로 이루어진 앱을 Compose로 Step by Step으로 천천히 마이그레이션 하는 방법을 다룹니다. 또한, 기존 뷰 시스템에서 제공중인 UI 컴포넌트를 Compose에서 똑같이 사용할 수 있는지, 어떤 컴포넌트는 없어서 만들어서 사용해야 하는지 등도 함께 다룹니다.
2. 마이그레이션 하기 전 Compose 환경 만들기 ❏ 3. Compose용 디자인 시스템 구축하기 (a.k.a 공통 컴포넌트) ❏ 4. 실제 마이그레이션 하기 ❏ Case 1. 아예 새로운 화면을 만들 때 ❏ Case 2. 기존 XML 뷰에 Compose 뷰 추가하기 혹은 기존 컴포넌트 교체하기 ❏ Case 3. RecyclerView를 컴포즈 리스트로 교체하기 (ViewHolder 아이템부터 전체 뷰까지) ❏ Case 4. Compose 에서 지원하지 않는 뷰 (ex. TextureView / VideoPlayer) 호스팅 하기 ❏ Case 5. 기타 안드로이드 구성요소 (ex. Context, Dialog, BottomSheet)를 Compose에서 사용하기
Compose 개발 팀에서 충분히 고려됨 ❏ Compose는 기존 안드로이드의 뷰 시스템과 호환 가능하도록 설계됨 ❏ Compose에 대한 이해가 개인 / 팀 차원에서 얼라인 되어야 원활하게 마이그레이션 가능 ❏ 사람마다 개개인이 느끼는 난이도와 학습 속도가 다름 ❏ 그렇기 때문에 한번에 우다다 바꾸고 팀과 싱크가 맞지 않으면 유지보수에 어려울 수도 있음 ❏ 이건 Compose 도입 차원 뿐만 아니라 새로운 기술을 사용하여 기존 코드를 마이그레이션 하는 모든 경우에 해당
화면에서 Compose 사용하기 ❏ Step 2. 기존 뷰 시스템 (not RecyclerView) 으로 이루어진 화면에서 새로운 컴포넌트만 Compose 사용해서 코드베이스 공존시키기 ❏ Step 3. 기존 뷰 시스템으로 이루어진 컴포넌트를 Compose 컴포넌트로 교체하기
화면에서 Compose 사용하기 ❏ Step 2. 기존 뷰 시스템 (not RecyclerView) 으로 이루어진 화면에서 새로운 컴포넌트만 Compose 사용해서 코드베이스 공존시키기 ❏ Step 3. 기존 뷰 시스템으로 이루어진 컴포넌트를 Compose 컴포넌트로 교체하기 ❏ Step 4. RecyclerView의 각 ViewHolder Item을 Compose 컴포넌트로 교체하기
화면에서 Compose 사용하기 ❏ Step 2. 기존 뷰 시스템 (not RecyclerView) 으로 이루어진 화면에서 새로운 컴포넌트만 Compose 사용해서 코드베이스 공존시키기 ❏ Step 3. 기존 뷰 시스템으로 이루어진 컴포넌트를 Compose 컴포넌트로 교체하기 ❏ Step 4. RecyclerView의 각 ViewHolder Item을 Compose 컴포넌트로 교체하기 ❏ Step 5. Step 3~4가 모두 완료됐다면 해당 화면 모두 Compose 기반으로 바꾸기
// (optional) Material2 사용 할 경우 implementation("androidx.compose.material:material") // Material Design 사용하지 않고 직접 만들 경우 implementation("androidx.compose.foundation:foundation") // Compose UI implementation("androidx.compose.ui:ui") } Compose를 위한 Gradle 설정하기 (의존성)
Fragment를 사용한다고 가정했을 때 ❏ 컬러 팔레트(Light Mode / Dark Mode) / Typography 등을 일괄적으로 적용할 수 있는 Base Activity, Base Fragment를 만들어서 Compose를 사용하는 모든 화면에 대해서 적용하기
/ Fragment…) ❏ BaseComposeActivity, BaseComposeFragment ❏ (제 경험상) 만약 ʻ우리 팀이 Compose가 이제 기본이다’ 라는 의사결정이 내린 상황이라면 ❏ Compose를 사용하는 화면의 베이스 클래스의 이름을 BaseActivity / BaseFragment 같은걸로 하고 ❏ 데이터바인딩을 사용하는 화면을 BaseBindingActivity / BaseBindingFragment 같은걸로 설정했었음
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) /// 생략 setContent { MyAppTheme { Content() } } } } Compose 사용 환경 만들기 (테마 시스템 적용)
Theme 2 (or 3)을 사용하지 않고 직접 만드는 경우 ❏ Compose Material에 있는 MaterialTheme에서 제공하는 Typography / ColorPalette를 직접 앱의 디자인에 맞게 아예 커스텀 할 경우 → 이 경우에는 자체적인 Compose용 테마를 만들어야 함
Color, /* ... */ val gradient: List<Color> /* ... */ ) @Immutable data class MyAppTypography( val fontFamily: FontFamily, val textStyle: TextStyle, ) Compose 자체 커스텀 테마 시스템 만들기 앱에서 사용하는 컬러 팔레트 예시 (Figma)
LocalColorPalette.current val typography: CustomTypography @Composable get() = LocalTypography.current } // 사용법 MyAppTheme.colors.mono100 Compose 자체 커스텀 테마 시스템 만들기
Compose Interop API 중 XML-Based Layout 에서 사용할 수 있는 ComposeView API 지원 ㄴ androidx.compose.ui.platform.ComposeView ❏ 화면 중 일부의 컴포넌트에 대해서만 Compose로 작성하고, ComposeView로 Wrapping 하여 XML에 배치
android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/appbar" app:layout_constraintBottom_toBottomOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> 마이그레이션 - II. 기존 XML 뷰에 Compose 뷰 추가하기
사용했던 커스텀 뷰의 문제는 난이도가 높고, 직접 Canvas로 그려주거나 해야하는데 Canvas API가 무엇보다도 어렵고 구현해야 할 코드의 양도 많음 ❏ 이미 커스텀 뷰가 많은 영역에서 사용중이어서 마이그레이션에 허들을 느꼈다면 해당 커스텀 뷰 자체를 일단 Compose로 만들어서 적용하는 것도 선택지임
사용했던 커스텀 뷰의 문제는 난이도가 높고, 직접 Canvas로 그려주거나 해야하는데 Canvas API가 무엇보다도 어렵고 구현해야 할 코드의 양도 많음 ❏ 이미 커스텀 뷰가 많은 영역에서 사용중이어서 마이그레이션에 허들을 느꼈다면 해당 커스텀 뷰 자체를 일단 Compose로 만들어서 적용하는 것도 선택지임 ❏ 커스텀 뷰의 내부 구현체를 AbstractComposeView로 변경한 후 Canvas API 혹은 ViewGroup.addView 같은 코드를 모두 Compose 컴포넌트로 변경
LazyColumn { items(100) { index -> AndroidView( modifier = Modifier.fillMaxSize(), factory = { context -> MyView(context) }, update = { view -> view.selectedItem = index }, onReset = { view -> view.clear() } ) } } } onReset Compose LazyList 시스템 상에서 해당 뷰가 재사용 될 때의 콜백 onRelease Compose LazyList 시스템 상에서 해당 뷰가 더이상 사용되지 않고 없어지려고 할 때 콜백
onSystemEvent: (intent: Intent?) -> Unit ) { val context = LocalContext.current val currentOnSystemEvent by rememberUpdatedState(onSystemEvent) DisposableEffect(context, systemAction) { val intentFilter = IntentFilter(systemAction) val broadcast = object : BroadcastReceiver() {
화면에서 Compose 사용하기 ❏ Step 2. 기존 뷰 시스템 (not RecyclerView) 으로 이루어진 화면에서 새로운 컴포넌트만 Compose 사용해서 코드베이스 공존시키기 ❏ Step 3. 기존 뷰 시스템으로 이루어진 컴포넌트를 Compose 컴포넌트로 교체하기 ❏ Step 4. RecyclerView의 각 ViewHolder Item을 Compose 컴포넌트로 교체하기 ❏ Step 5. Step 3~4가 모두 완료됐다면 해당 화면 모두 Compose 기반으로 바꾸기