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

Migrating a large-scale banking app to compose

Migrating a large-scale banking app to compose

The Android community adopts Jetpack Compose more and more each day. But what about our existing apps? Adopting existing apps to Jetpack Compose might be an overwhelming task when the migration is not planned well.

In this talk, we will see the Compose migration strategy used in DNB. We will briefly talk about the interoperability APIs and how they are used in this migration process. We will also talk about how we are adopting our internal design system to Jetpack Compose. Finally, we will discuss the challenges we faced during this migration.

Fatih Giriş

October 27, 2021
Tweet

More Decks by Fatih Giriş

Other Decks in Programming

Transcript

  1. Migrating a large-scale
    banking app to Compose
    S. Fatih Giris
    @fatih_grs

    View Slide

  2. Jetpack Compose

    View Slide

  3. How to migrate existing apps to
    Compose?

    View Slide

  4. Agenda
    • Integrating Compose
    • Interoperability APIs
    • Migration strategies
    • Migration at DNB
    • Take-aways

    View Slide

  5. Integrating Compose
    • Entirely Compose
    • View based UI + Compose
    • Composables inside Views
    • Views inside composables

    View Slide

  6. Interoperability APIs
    • ComponentActivity.setContent
    • ComposeView (View)
    • AndroidView (Composable)

    View Slide

  7. Migration Strategies
    • Top-down
    • Bottom-up

    View Slide

  8. Top-down
    • Start with the top most view
    • Go deeper and deeper ⬇

    View Slide

  9. Top-down
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

    fragment_top_down.xml

    View Slide

  10. Top-down
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    fragment_top_down.xml
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
    fancy_content.xml

    View Slide

  11. Top-down
    android:id="@+id/composeView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
    fancy_content.xml
    fragment_top_down.xml

    View Slide

  12. Top-down
    class TopDownFragment : Fragment() {
    override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
    ): View? {
    return inflater.inflate(
    R.layout.fragment_top_down,
    container,
    false
    )
    }
    }
    TopDownFragment.kt

    View Slide

  13. Top-down
    class TopDownFragment : Fragment() {
    override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
    ): View? {
    return ComposeView(requireContext())
    }
    }
    TopDownFragment.kt

    View Slide

  14. Top-down
    return ComposeView(requireContext())
    TopDownFragment.kt

    View Slide

  15. Top-down
    return ComposeView(requireContext()).apply {
    // Dispose the Composition when viewLifecycleOwner is destroyed
    setViewCompositionStrategy(
    ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner)
    )
    setContent {
    // Compose World
    }
    }
    TopDownFragment.kt

    View Slide

  16. Top-down
    return ComposeView(requireContext()).apply {
    // Dispose the Composition when viewLifecycleOwner is destroyed
    setViewCompositionStrategy(
    ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner)
    )
    setContent {
    // Compose World
    // Vertical Linear Layout
    Column {
    // Add views as AndroidView by creating
    // programatically or inflating from XML
    }
    }
    }
    TopDownFragment.kt

    View Slide

  17. Top-down
    setContent {
    // Vertical Linear Layout
    Column {
    }
    }
    TopDownFragment.kt

    View Slide

  18. Top-down
    setContent {
    // Vertical Linear Layout
    Column {
    AndroidView(
    modifier = Modifier.fillMaxSize(),
    factory = { context ->
    // Inflate it from XML
    inflater.inflate(
    R.layout.fancy_content,
    container,
    false
    )
    },
    update = { view ->
    // View's been inflated or state read in
    // this block has been updated
    }
    )
    }
    } TopDownFragment.kt

    View Slide

  19. Top-down
    setContent {
    // Vertical Linear Layout
    Column {
    AndroidView(
    modifier = Modifier.fillMaxSize(),
    factory = { context ->
    // Create view programatically
    // android.widget.Button
    Button(requireContext())
    },
    update = { view ->
    // View's been inflated or state read in
    // this block has been updated
    }
    )
    }
    }
    TopDownFragment.kt

    View Slide

  20. Top-down
    setContent {
    // Vertical Linear Layout
    Column {
    Button(onClick = {}) {
    Text("I am a compose button”)
    }
    }
    }
    TopDownFragment.kt

    View Slide

  21. Bottom-up
    • Start with the inner most UI elements
    • Go up and up ⬆

    View Slide

  22. Bottom-up
    fragment_bottom_up.xml
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

    View Slide

  23. Bottom-up
    fragment_bottom_up.xml
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    android:id="@+id/buttonComposeView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

    View Slide

  24. Bottom-up
    class BottomUpFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    buttonComposeView.apply {
    // Configure ComposeView
    }
    }
    }
    BottomUpFragment.kt

    View Slide

  25. Bottom-up
    buttonComposeView.apply {
    // Configure ComposeView
    }
    BottomUpFragment.kt

    View Slide

  26. Bottom-up
    buttonComposeView.apply {
    // Dispose the Composition when viewLifecycleOwner is destroyed
    setViewCompositionStrategy(
    ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner)
    )
    }
    BottomUpFragment.kt

    View Slide

  27. Bottom-up
    buttonComposeView.apply {
    // Dispose the Composition when viewLifecycleOwner is destroyed
    setViewCompositionStrategy(
    ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner)
    )
    setContent {
    // In Compose world
    }
    }
    BottomUpFragment.kt

    View Slide

  28. Bottom-up
    buttonComposeView.apply {
    // Dispose the Composition when viewLifecycleOwner is destroyed
    setViewCompositionStrategy(
    ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner)
    )
    setContent {
    // In Compose world
    Button(onClick = {}) {
    Text(text = "I am migrated Button")
    }
    }
    }
    BottomUpFragment.kt

    View Slide

  29. Bottom-up
    fragment_bottom_up.xml
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    android:id="@+id/buttonComposeView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

    View Slide

  30. Bottom-up
    fragment_bottom_up.xml
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    android:id="@+id/buttonComposeView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
    android:id="@+id/textComposeView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

    View Slide

  31. Bottom-up
    fragment_bottom_up.xml
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    View Slide

  32. Bottom-up
    fragment_bottom_up.xml
    android:id="@+id/fragmentComposeView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

    View Slide

  33. Voilà 🎉

    View Slide

  34. Design System at DNB
    • Eufemia Design System 🌈
    • Theming
    • Core components (EufemiaTextView, EufemiaButton etc.)

    View Slide

  35. Design System at DNB
    www.eufemia.dnb.no

    View Slide

  36. Design System at DNB
    • Tetris 📱(WIP)
    • Common UI components (Toolbar etc.)

    View Slide

  37. Design System at DNB
    Eufemia library

    View Slide

  38. Design System at DNB
    Eufemia library Tetris library
    uses

    View Slide

  39. Design System at DNB
    Tetris library
    uses

    View Slide

  40. Design System at DNB
    Eufemia
    App1
    App2
    uses
    uses

    View Slide

  41. Design System at DNB
    Eufemia
    Tetris
    (WIP)
    App1
    App2
    uses
    uses
    uses

    View Slide

  42. How?

    View Slide

  43. Migration Plan
    • Start with Eufemia & Tetris
    • Eufemia
    • Migrate design system to Compose (colours, shapes, typography)
    • Convert each component to Compose one by one
    • Tetris
    • Create Components as composables from the scratch
    • Use AndroidView for Eufemia Android Views

    View Slide

  44. Where to start?

    View Slide

  45. Migration Plan
    • Pick a screen in the app
    • Apply bottom-up approach
    • Create components in Tetris & Eufemia
    • Iterate for all screens

    View Slide

  46. During Migration
    Eufemia
    Compose
    Tetris
    Compose
    App1
    App2
    uses
    uses
    uses
    Eufemia
    uses

    View Slide

  47. After Migration
    Eufemia
    Compose
    Tetris
    Compose
    App1
    App2
    uses
    uses
    uses

    View Slide

  48. Current View Layer
    Activity + Fragment

    View Slide

  49. Current View Layer
    Activity + Fragment
    • Single Activity
    • Jetpack Navigation

    View Slide

  50. Expected View Layer
    • No Fragment
    • Activity sets content
    • Navigate between composables
    • Requires compose navigation
    library (if you don’t want to
    write your own)

    View Slide

  51. How to replace Fragments with Composables?

    View Slide

  52. We don’t for now

    View Slide

  53. Migration Plan
    Activity + Fragment + Compose

    View Slide

  54. Migration Plan
    • Only change the way views are
    presented (ComposeView +
    Composables)
    • No change in navigation
    • After all screens are migrated,
    remove fragments

    View Slide

  55. Compose POC

    View Slide

  56. Compose POC
    Toolbar
    Header
    Selectable item

    View Slide

  57. Compose POC
    • Android Studio: Bumblebee Canary 1
    • Android Gradle Plugin: 7.1.0-alpha01
    • Kotlin: 1.5.21
    • Compose: 1.0.1

    View Slide

  58. Compose POC
    Gradle Android cache fix plugin + AGP 7.1.0-alpha01 incompatibility
    ✅ Bypass version check or comment out the plugin :)

    View Slide

  59. Compose POC
    Hilt 2.38.1 + AGP 7.1.0-alpha01 bug*
    *https://github.com/google/dagger/issues/2618
    ✅ Update AGP to 7.1.0-alpha06

    View Slide

  60. Compose POC
    2 files found with path 'META-INF/ui_release.kotlin_module' from inputs:
    - /Users/fatih/.gradle/caches/transforms-3/90edb5b9f55f1931b87d2b5c8a7aa61d/…
    - /Users/fatih/.gradle/caches/transforms-3/52dda4f473a3d3633ea54e66766f28d2/…
    ✅ Exclude META-INF/ui_release.kotlin_module in packaging options

    View Slide

  61. Compose POC
    ✅ Update all Kotlin versions to the same version

    View Slide

  62. Compose POC
    Toolbar ✅
    Header ✅
    Selectable item ✅

    View Slide

  63. Compose POC
    ✅ Update androidx.activity version to 1.3.0

    View Slide

  64. Compose POC

    View Slide

  65. Migration Status
    • Eufemia
    • Theming: Colors, typography, shape ✅
    • Core components 🚧
    • Tetris 🚧
    • Apps 🚧

    View Slide

  66. Take-aways
    • Plan your migration
    • POC
    • Migrate slowly
    • Make use of interoperability APIs
    • Stick to the migration plan

    View Slide

  67. QUESTIONS
    @fatih_grs

    View Slide

  68. THANKS ♥

    View Slide