$30 off During Our Annual Pro Sale. View Details »

Droidknights 2023 - 이상훈 - 기존 앱을 Jetpack Compose로 마이그레이션 하기

Dora Lee
September 12, 2023

Droidknights 2023 - 이상훈 - 기존 앱을 Jetpack Compose로 마이그레이션 하기

기존 안드로이드 뷰 시스템 (XML, RecyclerView 등) 으로 이루어진 앱을 Compose로 Step by Step으로 천천히 마이그레이션 하는 방법을 다룹니다. 또한, 기존 뷰 시스템에서 제공중인 UI 컴포넌트를 Compose에서 똑같이 사용할 수 있는지, 어떤 컴포넌트는 없어서 만들어서 사용해야 하는지 등도 함께 다룹니다.

Dora Lee

September 12, 2023
Tweet

More Decks by Dora Lee

Other Decks in Technology

Transcript

  1. 기존 앱을 Jetpack Compose로
    마이그레이션 하기
    Sanghun Lee / Mod-haus Entertainment

    View Slide

  2. 저를 소개합니다!
    이상훈 (Dora Lee)
    Mod-haus Entertainment
    Android Team Lead
    @dsa28s

    View Slide

  3. Index
    ❏ 1. 기존 앱 → 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에서 사용하기

    View Slide

  4. 기존 앱 → Compose
    마이그레이션 전략
    1.

    View Slide

  5. Compose를 도입하기로 했는데... 방향성을 못잡겠다면?
    ❏ 기존 뷰 시스템 (ex. XML Based View Layout, Fragment etc…) 을
    사용하는 우리 앱에서 Compose를 도입할까 하는데 어떡하죠?

    View Slide

  6. Compose를 도입하기로 했는데... 방향성을 못잡겠다면?
    ❏ 기존 뷰 시스템 (ex. XML Based View Layout, Fragment etc…) 을
    사용하는 우리 앱에서 Compose를 도입할까 하는데 어떡하죠?
    ❏ 점진적으로 우리 앱은 Compose 기반의 앱으로 마이그레이션 할 계획이 있어요

    View Slide

  7. Compose로 한번에 마이그레이션 할 생각 하지말기
    ❏ 이러한 고민은 이미 Compose 개발 팀에서 충분히 고려됨
    ❏ Compose는 기존 안드로이드의 뷰 시스템과 호환 가능하도록 설계됨
    ❏ Compose에 대한 이해가 개인 / 팀 차원에서 얼라인 되어야
    원활하게 마이그레이션 가능
    ❏ 사람마다 개개인이 느끼는 난이도와 학습 속도가 다름
    ❏ 그렇기 때문에 한번에 우다다 바꾸고 팀과 싱크가 맞지 않으면 유지보수에 어려울 수도 있음
    ❏ 이건 Compose 도입 차원 뿐만 아니라 새로운 기술을 사용하여 기존 코드를 마이그레이션 하는 모든 경우에 해당

    View Slide

  8. 부담없이 Compose로 천천히 마이그레이션 하는 전략
    ❏ Step 1. 새로운 화면에서 Compose 사용하기

    View Slide

  9. 부담없이 Compose로 천천히 마이그레이션 하는 전략
    ❏ Step 1. 새로운 화면에서 Compose 사용하기
    ❏ Step 2. 기존 뷰 시스템 (not RecyclerView) 으로 이루어진 화면에서
    새로운 컴포넌트만 Compose 사용해서 코드베이스 공존시키기

    View Slide

  10. 부담없이 Compose로 천천히 마이그레이션 하는 전략
    ❏ Step 1. 새로운 화면에서 Compose 사용하기
    ❏ Step 2. 기존 뷰 시스템 (not RecyclerView) 으로 이루어진 화면에서
    새로운 컴포넌트만 Compose 사용해서 코드베이스 공존시키기
    ❏ Step 3. 기존 뷰 시스템으로 이루어진 컴포넌트를 Compose 컴포넌트로 교체하기

    View Slide

  11. 부담없이 Compose로 천천히 마이그레이션 하는 전략
    ❏ Step 1. 새로운 화면에서 Compose 사용하기
    ❏ Step 2. 기존 뷰 시스템 (not RecyclerView) 으로 이루어진 화면에서
    새로운 컴포넌트만 Compose 사용해서 코드베이스 공존시키기
    ❏ Step 3. 기존 뷰 시스템으로 이루어진 컴포넌트를 Compose 컴포넌트로 교체하기
    ❏ Step 4. RecyclerView의 각 ViewHolder Item을 Compose 컴포넌트로 교체하기

    View Slide

  12. 부담없이 Compose로 천천히 마이그레이션 하는 전략
    ❏ Step 1. 새로운 화면에서 Compose 사용하기
    ❏ Step 2. 기존 뷰 시스템 (not RecyclerView) 으로 이루어진 화면에서
    새로운 컴포넌트만 Compose 사용해서 코드베이스 공존시키기
    ❏ Step 3. 기존 뷰 시스템으로 이루어진 컴포넌트를 Compose 컴포넌트로 교체하기
    ❏ Step 4. RecyclerView의 각 ViewHolder Item을 Compose 컴포넌트로 교체하기
    ❏ Step 5. Step 3~4가 모두 완료됐다면 해당 화면 모두 Compose 기반으로 바꾸기

    View Slide

  13. Step 5까지 완료됐다면??
    뷰 마이그레이션 완료!
    화면 외 부수적인 것들 (ex. Analytics)등은
    고려되지 않았습니다

    View Slide

  14. 그럼 이제 본격적으로
    마이그레이션을 해볼까요?

    View Slide

  15. 마이그레이션 전
    Compose 환경 만들기
    2.

    View Slide

  16. build.gradle.kts
    android {
    buildFeatures {
    compose = true
    }
    composeOptions {
    kotlinCompilerExtensionVersion = "1.5.2" // 2023-08-24 기준
    }
    }
    Compose를 위한 Gradle 설정하기

    View Slide

  17. build.gradle.kts
    dependencies {
    val composePlatform = platform("androidx.compose:compose-bom:2023.08.00")
    implementation(composePlatform)
    // (선택) Compose 테스트 할 경우
    androidTestImplementation(composePlatform)
    // 이하 생략
    }
    Compose를 위한 Gradle 설정하기 (의존성)

    View Slide

  18. build.gradle.kts
    dependencies {
    // (optional) Material3 사용 할 경우
    implementation("androidx.compose.material3:material3")
    // (optional) Material2 사용 할 경우
    implementation("androidx.compose.material:material")
    // Material Design 사용하지 않고 직접 만들 경우
    implementation("androidx.compose.foundation:foundation")
    // Compose UI
    implementation("androidx.compose.ui:ui")
    }
    Compose를 위한 Gradle 설정하기 (의존성)

    View Slide

  19. build.gradle.kts
    dependencies {
    // (optional) Activity <-> Compose 상호작용
    implementation("androidx.activity:activity-compose:1.7.3")
    // (optional) Compose에서 ViewModel 사용하기
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
    // (optional) Compose에서 LiveData 사용하기
    implementation("androidx.compose.runtime:runtime-livedata")
    // (optional) Compose에서 RxJava 사용하기
    implementation("androidx.compose.runtime:runtime-rxjava2")
    }
    Compose를 위한 Gradle 설정하기 (의존성)

    View Slide

  20. Compose 테마 시스템 만들기
    ❏ 컬러 팔레트
    ❏ 폰트 (Typography)
    ❏ 컴포즈 용 앱 자체 테마 (Material 2 or Material 3)

    View Slide

  21. Compose 사용 환경 만들기
    ❏ 앱에서 여러 개의 Activity / Fragment를 사용한다고 가정했을 때
    ❏ 컬러 팔레트(Light Mode / Dark Mode) / Typography 등을 일괄적으로 적용할 수
    있는 Base Activity, Base Fragment를 만들어서 Compose를 사용하는 모든
    화면에 대해서 적용하기

    View Slide

  22. Compose 사용 환경 만들기
    ❏ ex. 기존 레이아웃 시스템을 사용하는 화면 (Activity / Fragment...)
    ❏ BaseActivity, BaseFragment
    ❏ 데이터 바인딩을 사용하는 화면인 경우
    ❏ (제 경험상) BaseBindingActivity, BaseBindingFragment

    View Slide

  23. Compose 사용 환경 만들기
    ❏ ex. Compose를 사용하는 화면 (Activity / Fragment…)
    ❏ BaseComposeActivity, BaseComposeFragment
    ❏ (제 경험상) 만약 ʻ우리 팀이 Compose가 이제 기본이다’ 라는
    의사결정이 내린 상황이라면
    ❏ Compose를 사용하는 화면의 베이스 클래스의 이름을 BaseActivity /
    BaseFragment 같은걸로 하고
    ❏ 데이터바인딩을 사용하는 화면을 BaseBindingActivity /
    BaseBindingFragment 같은걸로 설정했었음

    View Slide

  24. BaseComposeActivity.kt
    abstract class BaseComposeActivity: ComponentActivity() {
    @Composable
    abstract fun Content()
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    /// 생략
    setContent {
    Content()
    }
    }
    }
    Compose 사용 환경 만들기

    View Slide

  25. MainFeedListActivity.kt
    class MainFeedListActivity: BaseComposeActivity() {
    @Composable
    override fun Content() {
    MyAppTheme {
    // 배치
    }
    }
    }
    Compose 사용 환경 만들기

    View Slide

  26. BaseComposeFragment.kt
    abstract class BaseComposeFragment : Fragment() {
    @Composable
    abstract fun Content()
    override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?
    ): View? {
    return ComposeView(requireContext()).apply {
    setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
    setContent {
    Content()
    }
    }
    }
    }
    Compose 사용 환경 만들기

    View Slide

  27. MainHomeFragment.kt
    class MainHomeFragment: BaseComposeFragment() {
    @Composable
    override fun Content() {
    MyAppTheme {
    // 배치
    }
    }
    }
    Compose 사용 환경 만들기

    View Slide

  28. Compose 테마 시스템 만들기 - 색상 팔레트
    ❏ 기존에 정의되어 있는 XML Resources →
    Compose Color System으로 재정의 하기

    View Slide

  29. colors.xml

    #00ffff
    #ffa500
    #008000

    Compose 테마 시스템 만들기 - 색상 팔레트
    Theme.kt
    val PrimaryRed = Color(0xff00ffff)
    val PrimaryOrange = Color(0xffffa500)
    val PrimaryGreen = Color(0xff008000)

    View Slide

  30. Compose 테마 시스템 만들기 - 색상 팔레트 (m2)
    Theme.kt
    val PrimaryRed = Color(0xff00ffff)
    val PrimaryOrange = Color(0xffffa500)
    val PrimaryGreen = Color(0xff008000)
    private val DarkColors = darkColors(
    primary = PrimaryRed,
    secondary = PrimaryOrange,
    // ...
    )
    private val LightColors = lightColors(
    primary = PrimaryRed100,
    primaryVariant = Yellow400,
    // ...
    )
    다크 모드 팔레트
    라이트 모드 팔레트 정의

    View Slide

  31. Compose 테마 시스템 만들기 - 색상 팔레트 (m3)
    Theme.kt
    val PrimaryRed = Color(0xff00ffff)
    val PrimaryOrange = Color(0xffffa500)
    val PrimaryGreen = Color(0xff008000)
    private val DarkColors = darkColorScheme(
    primary = PrimaryRed,
    onPrimary = PrimaryOrange,
    // ...
    )
    private val LightColors = lightColorScheme(
    primary = PrimaryRed100,
    onPrimary = Yellow400,
    // ...
    )
    다크 모드 팔레트
    라이트 모드 팔레트 정의

    View Slide

  32. Compose 테마 시스템 만들기 - 색상 팔레트
    Theme.kt
    private val DarkColors = darkColors(...)
    private val LightColors = lightColors(...)
    @Composable
    fun MyAppTheme(
    content: @Composable () -> Unit,
    ) {
    MaterialTheme(
    colors = if (isSystemInDarkTheme()) DarkColors else LightColors,
    ) {
    content()
    }
    }
    테마를 일관적으로 적용하기
    위해 공통 Compose
    Component 만들기

    View Slide

  33. Compose 테마 시스템 만들기 - 색상 팔레트
    Theme.kt
    private val DarkColors = darkColors(...)
    private val LightColors = lightColors(...)
    @Composable
    fun MyAppTheme(
    content: @Composable () -> Unit,
    ) {
    MaterialTheme(
    colors = if (isSystemInDarkTheme()) DarkColors else LightColors,
    ) {
    content()
    }
    }
    isSystemInDarkTheme()
    시스템이 현재 다크모드로
    설정되어있는지 가져오는
    Compose 함수

    View Slide

  34. Compose 테마 시스템 만들기 - 폰트 (Typography)
    ❏ 기존에 정의되어 있는 XML Resources →
    Compose Typography System으로 재정의 하기
    ❏ res/fonts/* 에 들어있는 파일을 FontWeight에 따라서
    Compose Typography System으로 재정의 하기

    View Slide

  35. Compose 테마 시스템 만들기 - 폰트 (Typography)
    Typography.kt
    internal val pretendard = FontFamily(
    Font(R.font.pretendard_normal),
    Font(R.font.pretendard_medium, FontWeight.W500),
    Font(R.font.pretendard_semibold, FontWeight.SemiBold)
    )

    View Slide

  36. Compose 테마 시스템 만들기 - 폰트 (Typography)
    Typography.kt
    internal val pretendard = FontFamily(
    Font(R.font.pretendard_normal),
    Font(R.font.pretendard_medium, FontWeight.W500),
    Font(R.font.pretendard_semibold, FontWeight.SemiBold)
    )
    val myAppTypography = Typography(
    h1 = TextStyle(
    fontFamily = pretendard,
    fontWeight = FontWeight.W300,
    fontSize = 96.sp
    ),
    /*...*/
    )

    View Slide

  37. Compose 테마 시스템 만들기 - 폰트 (Typography)
    Theme.kt
    @Composable
    fun MyAppTheme(
    content: @Composable () -> Unit,
    ) {
    MaterialTheme(
    colors = if (isSystemInDarkTheme()) DarkColors else LightColors,
    typography = myAppTypography,
    ) {
    content()
    }
    }
    Compose 전체 테마에
    Typography 적용

    View Slide

  38. BaseComposeActivity.kt
    abstract class BaseComposeActivity: ComponentActivity() {
    @Composable
    abstract fun Content()
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    /// 생략
    setContent {
    MyAppTheme {
    Content()
    }
    }
    }
    }
    Compose 사용 환경 만들기 (테마 시스템 적용)

    View Slide

  39. BaseComposeFragment.kt
    abstract class BaseComposeFragment : Fragment() {
    /// 생략
    override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View? {
    return ComposeView(requireContext()).apply {
    setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
    setContent {
    MyAppTheme {
    Content()
    }
    }
    }
    }
    Compose 사용 환경 만들기

    View Slide

  40. 그런데 만약
    Material 테마를
    사용하지 않는다면?

    View Slide

  41. Compose 테마 시스템 만들기 - 자체 커스텀 테마
    ❏ Material Theme 2 (or 3)을 사용하지 않고 직접 만드는 경우
    ❏ Compose Material에 있는 MaterialTheme에서 제공하는
    Typography / ColorPalette를 직접 앱의 디자인에 맞게 아예 커스텀 할 경우
    → 이 경우에는 자체적인 Compose용 테마를 만들어야 함

    View Slide

  42. ThemeSystem.kt
    @Immutable
    data class MyAppColorPalette(
    val mono100: Color,
    val mono200: Color,
    /* ... */
    val gradient: List
    /* ... */
    )
    @Immutable
    data class MyAppTypography(
    val fontFamily: FontFamily,
    val textStyle: TextStyle,
    )
    Compose 자체 커스텀 테마 시스템 만들기
    앱에서 사용하는 컬러 팔레트 예시
    (Figma)

    View Slide

  43. ThemeSystem.kt
    val LocalColorPalette = staticCompositionLocalOf {
    MyAppColorPalette(
    mono100 = Color.Unspecified,
    mono200 = Color.Unspecified,
    // ...
    gradient = emptyList()
    )
    }
    val LocalTypography = staticCompositionLocalOf {
    MyAppTypography(
    fontFamily = FontFamily.Default,
    textStyle = TextStyle.Default
    )
    }
    Compose 자체 커스텀 테마 시스템 만들기

    View Slide

  44. Theme.kt (Page 1/2)
    @Composable
    fun MyAppTheme(
    content: @Composable () -> Unit
    ) {
    val colorPalette = MyAppColorPalette(
    mono100 = Color(0xff000000),
    // ...
    )
    val typography = MyAppTypography(
    fontFamily = pretendard,
    textStyle = TextStyle(fontSize = 18.sp)
    )
    //…
    Compose 자체 커스텀 테마 시스템 만들기

    View Slide

  45. Theme.kt (Page 2/2)
    @Composable
    fun MyAppTheme(
    content: @Composable () -> Unit
    ) {
    /* ... */
    CompositionLocalProvider(
    LocalColorPalette provides colorSystem,
    LocalTypography provides typographySystem,
    /* ... */
    content = content
    )
    }
    Compose 자체 커스텀 테마 시스템 만들기

    View Slide

  46. MyAppTheme.kt
    object MyAppTheme {
    val colors: MyAppColorPalette
    @Composable
    get() = LocalColorPalette.current
    val typography: CustomTypography
    @Composable
    get() = LocalTypography.current
    }
    // 사용법
    MyAppTheme.colors.mono100
    Compose 자체 커스텀 테마 시스템 만들기

    View Slide

  47. Compose 용
    디자인 시스템 구축하기
    3.

    View Slide

  48. Compose 용 디자인 시스템 구축하기
    ❏ 기존 XML에 작성된 컴포넌트 혹은 커스텀 뷰를 Compose에서 사용할 수 있도록
    공통화 하기

    View Slide

  49. Compose 용 디자인 시스템
    구축하기 (예시)
    ❏ CosmoAppBar
    ❏ CosmoTabRow
    ❏ CosmoTab
    Image goes here...

    View Slide

  50. ArtistFollowCard.kt
    @Composable
    fun ArtistFollowCard(
    modifier: Modifier = Modifier,
    artistName: String,
    artistImageUrl: String,
    isFollowing: Boolean = false,
    onFollowButtonClick: () -> Unit,
    onFollowingButtonClick: () -> Unit,
    usingCheckIcon: Boolean = false,
    ) {
    // ...
    }
    Compose 용 디자인 시스템 구축하기 (예시)

    View Slide

  51. ArtistFollowCard.kt
    @Composable
    fun ArtistFollowCard(
    modifier: Modifier = Modifier,
    artistName: String,
    artistImageUrl: String,
    isFollowing: Boolean = false,
    onFollowButtonClick: () -> Unit,
    onFollowingButtonClick: () -> Unit,
    usingCheckIcon: Boolean = false,
    ) {
    // ...
    }
    Compose 용 디자인 시스템 구축하기 (예시)

    View Slide

  52. Compose 용 디자인 시스템 구축하기
    ❏ 기존 XML에 작성된 컴포넌트 혹은 커스텀 뷰를 Compose에서 사용할 수 있도록
    공통화 하기
    ❏ 처음부터 모든 컴포넌트를 Compose로 만들려고 시도하지 않기
    ❏ 마이그레이션 하는 화면에서 쓰이는 컴포넌트 먼저 공통화
    ❏ 새로 만드는 컴포넌트 공통화

    View Slide

  53. Compose 용 디자인 시스템 구축하기 - NowInAndroid
    ❏ android/nowinandroid 프로젝트를 예시로 들면
    :core:designsystem 이라는 모듈을 만들고, 거기서 NowInAndroid 앱에서
    사용되는 공통 Compose 컴포넌트를 모아놨음

    View Slide

  54. Compose 용 디자인 시스템 구축하기 - NowInAndroid
    ❏ android/nowinandroid 프로젝트를 예시로 들면
    :core:designsystem 이라는 모듈을 만들고, 거기서 NowInAndroid 앱에서
    사용되는 공통 Compose 컴포넌트를 모아놨음

    View Slide

  55. 실제 마이그레이션 하기
    (Existing Layout System → Compose)
    4.

    View Slide

  56. 실제 마이그레이션 하기
    I. 아예 새로운 화면 만들 때
    4.

    View Slide

  57. 마이그레이션 - I. 아예 새로운 화면 만들 때
    ❏ 아까 언급했던 Compose 기반의 신규 화면 같은 경우에는
    BaseComposeActivity / BaseComposeFragment 사용하여 처음부터
    Compose 로 구현하기

    View Slide

  58. 마이그레이션 - I. 아예 새로운 화면 만들 때
    ❏ 아까 언급했던 Compose 기반의 신규 화면 같은 경우에는
    BaseComposeActivity / BaseComposeFragment 사용하여 처음부터
    Compose 로 구현하기
    ❏ 만들어놨던 Compose 용 디자인 컴포넌트를 활용해서 화면 만들기

    View Slide

  59. NewScreenWithComposeActivity.kt
    class NewScreenWithComposeActivity: BaseComposeActivity() {
    @Composable
    override fun Content() {
    MyAppTheme {
    // 배치
    }
    }
    }
    마이그레이션 - I. 아예 새로운 화면 만들 때

    View Slide

  60. NewScreenWithComposeFragment.kt
    class NewScreenWithComposeFragment: BaseComposeFragment() {
    @Composable
    override fun Content() {
    MyAppTheme {
    // 배치
    }
    }
    }
    마이그레이션 - I. 아예 새로운 화면 만들 때

    View Slide

  61. 실제 마이그레이션 하기
    II. 기존 XML 뷰에 Compose 뷰 추가하기
    4.

    View Slide

  62. 마이그레이션 - II. 기존 XML 뷰에 Compose 뷰 추가하기
    ❏ Compose Interop API 중 XML-Based Layout 에서 사용할 수 있는
    ComposeView API 지원
    ㄴ androidx.compose.ui.platform.ComposeView
    ❏ 화면 중 일부의 컴포넌트에 대해서만 Compose로 작성하고,
    ComposeView로 Wrapping 하여 XML에 배치

    View Slide

  63. 마이그레이션 - II. 기존 XML 뷰에 Compose 뷰 추가하기
    activity_mixed_compose.xml
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:action="@{viewModel.action}">

    android:id="@+id/compose_view"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    app:layout_constraintTop_toBottomOf="@id/appbar"
    app:layout_constraintBottom_toBottomOf="parent" />


    View Slide

  64. activity_mixed_compose.xml
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:action="@{viewModel.action}">

    android:id="@+id/compose_view"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    app:layout_constraintTop_toBottomOf="@id/appbar"
    app:layout_constraintBottom_toBottomOf="parent" />


    마이그레이션 - II. 기존 XML 뷰에 Compose 뷰 추가하기

    View Slide

  65. activity_mixed_compose.xml
    android:id="@+id/compose_view"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    app:layout_constraintTop_toBottomOf="@id/appbar"
    app:layout_constraintBottom_toBottomOf="parent" />
    XML ComposeView 에 프리뷰 추가하기
    Preview.kt
    @Preview
    @Composable
    fun GreetingPreview() {
    Greeting(name = "Android")
    }

    View Slide

  66. XML ComposeView 에 프리뷰 추가하기
    activity_mixed_compose.xml
    android:id="@+id/compose_view"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    app:layout_constraintTop_toBottomOf="@id/appbar"
    app:layout_constraintBottom_toBottomOf="parent"
    tools:composableName="com.example.designsystem.HelloWorldComponent.GreetingPreview"
    />

    View Slide

  67. XML ComposeView 에 프리뷰 추가하기
    activity_mixed_compose.xml
    android:id="@+id/compose_view"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    app:layout_constraintTop_toBottomOf="@id/appbar"
    app:layout_constraintBottom_toBottomOf="parent"
    tools:composableName="com.example.designsystem.HelloWorldComponent.GreetingPreview"
    />

    View Slide

  68. 마이그레이션 - II. 기존 XML 뷰에 Compose 뷰 추가하기
    ExampleFragment.kt
    override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
    ): View {
    _binding = FragmentExampleBinding.inflate(inflater, container, false)
    val view = binding.root
    binding.composeView.apply {
    setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
    setContent {
    // 여기서부터 Compose 영역
    MyAppTheme {
    Text("여기는 Compose로 그려지는 영역입니다")
    }
    }
    }
    return view
    }

    View Slide

  69. 실제 마이그레이션 하기
    III. 기존 XML 뷰를 Compose 뷰로 교체하기
    4.

    View Slide

  70. 마이그레이션 - III. 기존 XML 뷰를 Compose 뷰로 교체하기
    sample_before.xml
    android:id="@+id/test_included_layout"
    layout="@layout/component_image_single"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:visibility="@{itemModel.images.size() == 1, default = visible}"
    app:handler="@{handler}" />

    View Slide

  71. 마이그레이션 - III. 기존 XML 뷰를 Compose 뷰로 교체하기
    sample_after.xml
    android:id="@+id/test_included_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:visibility="@{itemModel.images.size() == 1, default = visible}"
    tools:composableName="com.example.feature.phoeo.SinglePhoto.SinglePhotoPreview"
    />

    View Slide

  72. 마이그레이션 - III. 기존 XML 뷰를 Compose 뷰로 교체하기
    ❏ 컨테이너 기반의 레이아웃은 Compose의 Box, Column, Row 기반으로 만들기

    View Slide

  73. 마이그레이션 - III. 기존 XML 뷰를 Compose 뷰로 교체하기
    ❏ Column, Row 기반이면 Alignment, Arrangement 잘 사용하기

    View Slide

  74. 실제 마이그레이션 하기
    IV. 커스텀 뷰를 Compose 기반으로 만들기
    4.

    View Slide

  75. 마이그레이션 - IV. 커스텀 뷰를 Compose 기반으로 만들기
    ❏ 기존에 사용했던 커스텀 뷰의 문제는 난이도가 높고, 직접 Canvas로
    그려주거나 해야하는데 Canvas API가 무엇보다도 어렵고 구현해야 할
    코드의 양도 많음

    View Slide

  76. 마이그레이션 - IV. 커스텀 뷰를 Compose 기반으로 만들기
    ❏ 기존에 사용했던 커스텀 뷰의 문제는 난이도가 높고, 직접 Canvas로
    그려주거나 해야하는데 Canvas API가 무엇보다도 어렵고 구현해야 할
    코드의 양도 많음
    ❏ 이미 커스텀 뷰가 많은 영역에서 사용중이어서 마이그레이션에 허들을 느꼈다면
    해당 커스텀 뷰 자체를 일단 Compose로 만들어서 적용하는 것도 선택지임

    View Slide

  77. 마이그레이션 - IV. 커스텀 뷰를 Compose 기반으로 만들기
    ❏ 기존에 사용했던 커스텀 뷰의 문제는 난이도가 높고, 직접 Canvas로
    그려주거나 해야하는데 Canvas API가 무엇보다도 어렵고 구현해야 할
    코드의 양도 많음
    ❏ 이미 커스텀 뷰가 많은 영역에서 사용중이어서 마이그레이션에 허들을 느꼈다면
    해당 커스텀 뷰 자체를 일단 Compose로 만들어서 적용하는 것도 선택지임
    ❏ 커스텀 뷰의 내부 구현체를 AbstractComposeView로 변경한 후 Canvas API 혹은
    ViewGroup.addView 같은 코드를 모두 Compose 컴포넌트로 변경

    View Slide

  78. 마이그레이션 - IV. 커스텀 뷰를 Compose 기반으로 만들기
    CustomView.kt
    class CustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
    ) : View(context, attrs, defStyleAttr) {
    // 이하 생략
    }

    View Slide

  79. 마이그레이션 - IV. 커스텀 뷰를 Compose 기반으로 만들기
    CustomView.kt
    class CustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
    ) : AbstractComposeView(context, attrs, defStyleAttr) {
    // 이하 생략
    }

    View Slide

  80. 마이그레이션 - IV. 커스텀 뷰를 Compose 기반으로 만들기
    CustomView.kt
    class CustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
    ) : AbstractComposeView(context, attrs, defStyleAttr) {
    @Composable
    override fun Content() {
    CustomComponent()
    }
    }

    View Slide

  81. AbstractComposeView - XML 파라미터 주기
    CustomView.kt
    class CustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
    ) : AbstractComposeView(context, attrs, defStyleAttr) {
    var message by mutableStateOf("")
    @Composable
    override fun Content() {
    CustomComponent(message = message)
    }
    }

    View Slide

  82. AbstractComposeView - XML 파라미터 주기
    CustomView.kt
    init {
    attrs?.let {
    context.obtainStyledAttributes(it, R.styleable.CustomView).run {
    message = getString(R.styleable.CustomView_message)
    recycle()
    }
    }
    }

    View Slide

  83. XML ComposeView 에 프리뷰 추가하기
    activity_mixed_compose.xml
    android:id="@+id/custom_view"
    android:layout_width="match_parent"
    android:layout_height="100dp"
    app:message="XML에 파라미터 넘겼을 때 텍스트가 잘 표시되나?!"
    app:layout_constraintTop_toBottomOf="@id/appbar"
    app:layout_constraintBottom_toBottomOf="parent"
    />

    View Slide

  84. 실제 마이그레이션 하기
    V. RecyclerView → Compose
    4.

    View Slide

  85. 마이그레이션 - V. RecyclerView → Compose
    Reference
    https://medium.com/androiddevelop
    ers/jetpack-compose-interop-using
    -compose-in-a-recyclerview-569c7
    ec7a583

    View Slide

  86. 마이그레이션 - V. RecyclerView → Compose
    ❏ Step 1. RecyclerView의 각 ViewHolder를 ComposeView로 마이그레이션
    ❏ Step 2. RecyclerView를 Compose의 리스트 컴포넌트로 마이그레이션
    ❏ Case 1. LinearLayoutManager → LazyColumn / LazyRow
    ❏ Case 2. GridLayoutManager
    ㄴ LazyVerticalGrid / LazyHorizontalGrid
    ❏ Case 3. StaggeredGridLayoutManager
    ㄴ LazyVerticalStaggeredGrid / LazyHorizontalStaggeredGrid

    View Slide

  87. 마이그레이션 - V. RecyclerView → Compose
    ❏ Step 1. RecyclerView의 각 ViewHolder를 ComposeView로 마이그레이션
    ❏ Step 2. RecyclerView를 Compose의 리스트 컴포넌트로 마이그레이션
    ❏ Case 1. LinearLayoutManager → LazyColumn / LazyRow
    ❏ Case 2. GridLayoutManager
    ㄴ LazyVerticalGrid / LazyHorizontalGrid
    ❏ Case 3. StaggeredGridLayoutManager
    ㄴ LazyVerticalStaggeredGrid / LazyHorizontalStaggeredGrid

    View Slide

  88. RecyclerView의 각 ViewHolder Compose화 하기
    ComposeViewHolder.kt
    abstract class ComposeViewHolder(val composeView: ComposeView) : RecyclerView.ViewHolder(composeView) {
    init {
    composeView.setViewCompositionStrategy(
    ViewCompositionStrategy.DisposeOnDetachedFromWindowOrReleasedFromPool
    )
    }
    @Composable
    abstract fun Content()
    fun bind() {
    composeView.setContent {
    MyAppTheme {
    Content()
    }
    }
    }
    }

    View Slide

  89. RecyclerView의 각 ViewHolder Compose화 하기
    ComposeViewHolder.kt
    abstract class ComposeViewHolder(val composeView: ComposeView) : RecyclerView.ViewHolder(composeView) {
    init {
    composeView.setViewCompositionStrategy(
    ViewCompositionStrategy.DisposeOnDetachedFromWindowOrReleasedFromPool
    )
    }
    @Composable
    abstract fun Content()
    fun bind() {
    composeView.setContent {
    MyAppTheme {
    Content()
    }
    }
    }
    }

    View Slide

  90. RecyclerView의 각 ViewHolder Compose화 하기
    ComposeViewHolder.kt
    abstract class ComposeViewHolder(val composeView: ComposeView) : RecyclerView.ViewHolder(composeView) {
    init {
    composeView.setViewCompositionStrategy(
    ViewCompositionStrategy.DisposeOnDetachedFromWindowOrReleasedFromPool
    )
    }
    @Composable
    abstract fun Content()
    fun bind() {
    composeView.setContent {
    MyAppTheme {
    Content()
    }
    }
    }
    }
    Composition 전략 설정하기
    매우 중요!!

    View Slide

  91. Compose의 ViewCompositionStrategy
    ❏ 왜 사용할까?
    ❏ Compose Composition을 dispose 하는 방법 설정
    ❏ 적절한 시기에 해제를 하지 않으면 Memory Leak이 발생 할 수 있음

    View Slide

  92. Compose의 ViewCompositionStrategy
    ❏ DisposeOnDetachedFromWindow
    ❏ DisposeOnDetachedFromWindowOrReleasedFromPool
    ❏ DisposeOnLifecycleDestroyed
    ❏ DisposeOnViewTreeLifecycleDestroyed

    View Slide

  93. Compose의 ViewCompositionStrategy
    ❏ DisposeOnDetachedFromWindow
    ❏ DisposeOnDetachedFromWindowOrReleasedFromPool (Default)
    ❏ DisposeOnLifecycleDestroyed
    ❏ DisposeOnViewTreeLifecycleDestroyed

    View Slide

  94. Compose의 ViewCompositionStrategy
    ❏ DisposeOnDetachedFromWindow
    ❏ Compose 뷰 자체가 현재 Window로부터 detach 됐을 때 composition dispose
    ❏ Activity에서 ComposeView 를 사용하거나 ViewGroup.removeView~ 에서
    없어졌을 때 트리거

    View Slide

  95. Compose의 ViewCompositionStrategy
    ❏ DisposeOnDetachedFromWindowOrReleasedFromPool (Default)
    ❏ Compose 뷰 자체가 Pooling Container 기반 내에서 돌아갈 때 사용
    ❏ ex. RecyclerView
    ❏ RecyclerView 같은 경우 스크롤 중에 각 아이템에 대하여
    Composition을 자주 dispose + recreate하면 버벅거릴 수 있는데 이를 방지하기
    위함

    View Slide

  96. Compose의 ViewCompositionStrategy
    ❏ DisposeOnLifecycleDestroyed
    ❏ 해당 Strategy를 설정할 때 인자로 받는 Lifecycle이 destroy 됐을 때 해제
    ❏ Fragment 환경의 ComposeView에서 쓰기에 용이함

    View Slide

  97. Compose의 ViewCompositionStrategy
    ❏ DisposeOnViewTreeLifecycleDestroyed
    ❏ View가 attach 된 이후 현재의 Window 에서 ViewTreeLifecycleOwner가
    destroy 될 때 해제
    ❏ ComposeView를 사용하는 부분에서 Lifecycle를 모를 때 사용

    View Slide

  98. 다시 돌아와서...

    View Slide

  99. RecyclerView의 각 ViewHolder Compose화 하기
    ComposeViewHolder.kt
    abstract class ComposeViewHolder(val composeView: ComposeView) : RecyclerView.ViewHolder(composeView) {
    init {
    composeView.setViewCompositionStrategy(
    ViewCompositionStrategy.DisposeOnDetachedFromWindowOrReleasedFromPool
    )
    }
    @Composable
    abstract fun Content()
    fun bind() {
    composeView.setContent {
    MyAppTheme {
    Content()
    }
    }
    }
    }

    View Slide

  100. RecyclerView의 각 ViewHolder Compose화 하기
    HomeItemAdapter.kt
    class HomeItemAdapter : RecyclerView.Adapter() {
    class HomeItemViewHolder(
    val composeView: ComposeView
    ) : ComposeViewHolder(composeView) {
    @Composable
    override fun Content() {
    // 컴포즈 컴포넌트 그리기
    }
    }
    }

    View Slide

  101. RecyclerView의 각 ViewHolder Compose화 하기
    HomeItemAdapter.kt
    class HomeItemAdapter : RecyclerView.Adapter() {
    override fun onCreateViewHolder(
    parent: ViewGroup,
    viewType: Int,
    ): HomeItemViewHolder {
    return HomeItemViewHolder(ComposeView(parent.context))
    }
    }

    View Slide

  102. RecyclerView의 각 ViewHolder Compose화 하기
    HomeItemAdapter.kt
    class HomeItemAdapter : RecyclerView.Adapter() {
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    holder.bind()
    }
    }
    ComposeViewHolder 에서
    정의한 bind 함수 호출해서 실제
    ComposeView에 그리기

    View Slide

  103. 마이그레이션 - V. RecyclerView → Compose
    ❏ Step 1. RecyclerView의 각 ViewHolder를 ComposeView로 마이그레이션
    ❏ Step 2. RecyclerView를 Compose의 리스트 컴포넌트로 마이그레이션
    ❏ Case 1. LinearLayoutManager → LazyColumn / LazyRow
    ❏ Case 2. GridLayoutManager
    ㄴ LazyVerticalGrid / LazyHorizontalGrid
    ❏ Case 3. StaggeredGridLayoutManager
    ㄴ LazyVerticalStaggeredGrid / LazyHorizontalStaggeredGrid

    View Slide

  104. Reference
    https://velog.io/@blue-sky/Compose
    -%EB%AA%A9%EB%A1%9D-%EB%B
    0%8F-%EA%B7%B8%EB%A6%AC%
    EB%93%9C

    View Slide

  105. RecyclerView를 Compose 리스트 컴포넌트로 마이그레이션

    View Slide

  106. LazyColumn 예시
    LazyColumnExample.kt
    @Composable
    fun HomeFeedList(){
    LazyColumn {
    item {
    Text("Test1")
    }
    items(5) { index ->
    Text(text = "item :$index")
    }
    items(homeFeedList) { item ->
    HomeFeedItem(item = item)
    }
    }
    }

    View Slide

  107. LazyColumn 예시
    LazyColumnExample.kt
    @Composable
    fun HomeFeedList(){
    LazyColumn {
    item {
    Text("Test1")
    }
    items(5) { index ->
    Text(text = "item :$index")
    }
    items(homeFeedList) { item ->
    HomeFeedItem(item = item)
    }
    }
    }

    View Slide

  108. Lazy Grids 예시
    LazyGridExample.kt
    LazyVerticalGrid(
    columns = GridCells.Adaptive(minSize = 128.dp)
    ) {
    items(photos) { photo ->
    PhotoItem(photo)
    }
    }

    View Slide

  109. Lazy Staggered 예시
    LazyStaggeredGridExample.kt
    LazyVerticalStaggeredGrid(
    columns = StaggeredGridCells.Adaptive(200.dp),
    verticalItemSpacing = 4.dp,
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    content = {
    items(randomSizedPhotos) { photo ->
    AsyncImage(
    model = photo,
    contentScale = ContentScale.Crop,
    contentDescription = null,
    modifier = Modifier.fillMaxWidth().wrapContentHeight()
    )
    }
    },
    modifier = Modifier.fillMaxSize()
    )

    View Slide

  110. Compose LazyList + StickyHeader 예시
    LazyListStickyHeaderExample.kt
    val grouped = contacts.groupBy { it.firstName[0] }
    @OptIn(ExperimentalFoundationApi::class)
    @Composable
    fun ContactsList(grouped: Map>) {
    LazyColumn {
    grouped.forEach { (initial, contactsForInitial) ->
    stickyHeader {
    CharacterHeader(initial)
    }
    items(contactsForInitial) { contact ->
    ContactListItem(contact)
    }
    }
    }
    }

    View Slide

  111. 실제 마이그레이션 하기
    VI. Compose에서 지원하지 않는 뷰를
    Compose에서 호스팅하기
    4.

    View Slide

  112. 마이그레이션 - VI. Compose Interop
    ❏ Compose에서 기본적으로 지원하지 않는 뷰
    (ex. TextureView, SurfaceView, AdView 등)를 Compose 컴포넌트화 하기 위함

    View Slide

  113. 마이그레이션 - VI. Compose Interop
    ❏ Compose에서 기본적으로 지원하지 않는 뷰
    (ex. TextureView, SurfaceView, AdView 등)를 Compose 컴포넌트화 하기 위함
    ❏ AndroidView 라는 Compose Interop API를 지원함

    View Slide

  114. Compose Interop API - AndroidView
    AndroidViewExample.kt
    @Composable
    fun CustomView() {
    var selectedItem by remember { mutableStateOf(0) }
    AndroidView(
    modifier = Modifier.fillMaxSize(), // Compose UI 트리에 뷰 크기 설정
    factory = { context ->
    CustomView(context).apply { // 뷰 초기화
    setOnClickListener {
    selectedItem = 1
    }
    }
    },
    update = { view ->
    // 뷰가 업데이트 되었거나 Compose UI 트리가 업데이트 되는 경우
    view.selectedItem = selectedItem
    }
    )
    }

    View Slide

  115. Compose Interop API - AndroidView
    AndroidViewExample.kt
    @Composable
    fun CustomView() {
    var selectedItem by remember { mutableStateOf(0) }
    AndroidView(
    modifier = Modifier.fillMaxSize(), // Compose UI 트리에 뷰 크기 설정
    factory = { context ->
    CustomView(context).apply { // 뷰 초기화
    setOnClickListener {
    selectedItem = 1
    }
    }
    },
    update = { view ->
    // 뷰가 업데이트 되었거나 Compose UI 트리가 업데이트 되는 경우
    view.selectedItem = selectedItem
    }
    )
    }

    View Slide

  116. 이런 경우가 있을지 모르겠지만...
    만약 Compose 환경에서
    ViewBinding을 사용하고 싶다면...?

    View Slide

  117. 놀랍게도 API가 있습니다

    View Slide

  118. Compose Interop API - AndroidViewBinding
    AndroidViewBindingExample.kt
    @Composable
    fun AndroidViewBindingExample() {
    AndroidViewBinding(ExampleLayoutBinding::inflate) {
    exampleView.setBackgroundColor(Color.GRAY)
    }
    }

    View Slide

  119. 번외로... 만약 AndroidView를
    Compose의 LazyList 에서도 사용할 수
    있을까요?

    View Slide

  120. 가능합니다

    View Slide

  121. AndroidView in Compose LazyList
    AndroidViewInLazyList.kt
    @OptIn(ExperimentalComposeUiApi::class)
    @Composable
    fun AndroidViewInLazyList() {
    LazyColumn {
    items(100) { index ->
    AndroidView(
    modifier = Modifier.fillMaxSize(),
    factory = { context ->
    MyView(context)
    },
    update = { view ->
    view.selectedItem = index
    },
    onReset = { view ->
    view.clear()
    }
    )
    }
    }
    }

    View Slide

  122. AndroidView in Compose LazyList
    AndroidViewInLazyList.kt
    @OptIn(ExperimentalComposeUiApi::class)
    @Composable
    fun AndroidViewInLazyList() {
    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 시스템 상에서
    해당 뷰가 더이상 사용되지 않고 없어지려고
    할 때 콜백

    View Slide

  123. 실제 마이그레이션 하기
    VII. 기타 안드로이드 구성요소를
    Compose에서 사용하기
    4.

    View Slide

  124. 마이그레이션 - VII. 기타 안드로이드 구성요소 사용하기
    ❏ 현재 Compose 뷰가 호스팅 되는 Context 접근하기
    ❏ LocalContext.current
    ❏ Bottom Sheet
    ❏ Compose Material2 / Material 3에서 사용
    ❏ (Alert)Dialog
    ❏ Compose Material2 / Material3에서 AlertDialog 컴포넌트 사용
    ❏ Compose Dialog 컴포넌트 사용

    View Slide

  125. 마이그레이션 - VII. 기타 안드로이드 구성요소 사용하기
    ❏ Toast 사용하기
    ❏ 현재 Compose 자체에서는 Toast가 지원되지 않음
    ❏ Third-party 라이브러리 사용하기
    ❏ 혹은 LocalContext.current 를 사용하여 토스트 메시지 띄우기

    View Slide

  126. Toast in Compose
    ComposeToastExample.kt
    @Composable
    fun CustomButton() {
    val context = LocalContext.current
    Button(
    onClick = {
    Toast.makeText(context, "이것은 토스트 메시지", Toast.LENGTH_LONG).show()
    }
    )
    }

    View Slide

  127. (번외) BroadcastReceiver in Compose
    ComposeBroadcastReceiverExample.kt
    @Composable
    fun SystemBroadcastReceiver(
    systemAction: String,
    onSystemEvent: (intent: Intent?) -> Unit
    ) {
    val context = LocalContext.current
    val currentOnSystemEvent by rememberUpdatedState(onSystemEvent)
    DisposableEffect(context, systemAction) {
    val intentFilter = IntentFilter(systemAction)
    val broadcast = object : BroadcastReceiver() {

    View Slide

  128. (번외) BroadcastReceiver in Compose
    ComposeBroadcastReceiverExample.kt
    @Composable
    fun SystemBroadcastReceiver(...) {
    // ... 생략
    DisposableEffect(context, systemAction) {
    val intentFilter = IntentFilter(systemAction)
    val broadcast = object : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
    currentOnSystemEvent(intent)
    }
    }
    context.registerReceiver(broadcast, intentFilter)
    onDispose {
    context.unregisterReceiver(broadcast)
    }
    }
    }

    View Slide

  129. (번외) BroadcastReceiver in Compose
    ComposeBroadcastReceiverExample.kt
    @Composable
    fun SystemBroadcastReceiver(...) {
    // ... 생략
    DisposableEffect(context, systemAction) {
    val intentFilter = IntentFilter(systemAction)
    val broadcast = object : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
    currentOnSystemEvent(intent)
    }
    }
    context.registerReceiver(broadcast, intentFilter)
    onDispose {
    context.unregisterReceiver(broadcast)
    }
    }
    }
    DisposableEffect
    Compose 시스템 상에서 해당
    컴포넌트가
    Disposable 되는 것을 캐치하는 Effect

    View Slide

  130. (번외) BroadcastReceiver in Compose
    ComposeBroadcastReceiverExample.kt
    @Composable
    fun SystemBroadcastReceiver(...) {
    // ... 생략
    DisposableEffect(context, systemAction) {
    val intentFilter = IntentFilter(systemAction)
    val broadcast = object : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
    currentOnSystemEvent(intent)
    }
    }
    context.registerReceiver(broadcast, intentFilter)
    onDispose {
    context.unregisterReceiver(broadcast)
    }
    }
    }
    해당 SystemBroadcastReceiver
    컴포넌트가 생길 때 1회 Register

    View Slide

  131. (번외) BroadcastReceiver in Compose
    ComposeBroadcastReceiverExample.kt
    @Composable
    fun SystemBroadcastReceiver(...) {
    // ... 생략
    DisposableEffect(context, systemAction) {
    val intentFilter = IntentFilter(systemAction)
    val broadcast = object : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
    currentOnSystemEvent(intent)
    }
    }
    context.registerReceiver(broadcast, intentFilter)
    onDispose {
    context.unregisterReceiver(broadcast)
    }
    }
    }
    해당 SystemBroadcastReceiver
    컴포넌트가 뷰 트리에서 없어질 때
    receiver 해제

    View Slide

  132. 부담없이 Compose로 천천히 마이그레이션 하는 전략
    ❏ Step 1. 새로운 화면에서 Compose 사용하기
    ❏ Step 2. 기존 뷰 시스템 (not RecyclerView) 으로 이루어진 화면에서
    새로운 컴포넌트만 Compose 사용해서 코드베이스 공존시키기
    ❏ Step 3. 기존 뷰 시스템으로 이루어진 컴포넌트를 Compose 컴포넌트로 교체하기
    ❏ Step 4. RecyclerView의 각 ViewHolder Item을 Compose 컴포넌트로 교체하기
    ❏ Step 5. Step 3~4가 모두 완료됐다면 해당 화면 모두 Compose 기반으로 바꾸기

    View Slide

  133. 감사합니다!

    View Slide