Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building Multiplatform Apps with Compose

Mohit S
April 25, 2023

Building Multiplatform Apps with Compose

Mohit S

April 25, 2023
Tweet

More Decks by Mohit S

Other Decks in Programming

Transcript

  1. Mohit Sarveiya
    Building Multiplatform Apps with Compose
    @[email protected]

    View Slide

  2. Building Multiplatform Apps with Compose
    ● Setup Project

    View Slide

  3. Building Multiplatform Apps with Compose
    ● Setup Project

    ● Share Compose UI

    View Slide

  4. Building Multiplatform Apps with Compose
    ● Setup Project

    ● Share Compose UI

    ● SwiftUI & Compose Interop

    View Slide

  5. Building Multiplatform Apps with Compose
    ● Setup Project

    ● Share Compose UI

    ● SwiftUI & Compose Interop

    ● Architecture & Navigation

    View Slide

  6. Android

    Kotlin/JVM
    iOS

    Swift/LLVM
    Web

    JS
    Desktop

    Kotlin/JVM

    View Slide

  7. Share

    View Slide

  8. API
    Share

    View Slide

  9. API
    Share
    Cache

    View Slide

  10. API
    Share
    Cache Business Logic

    View Slide

  11. API
    Share
    Cache Business Logic UI Components

    View Slide

  12. https:
    /
    /
    github.com/JetBrains/compose-multiplatform
    compose-multiplatform

    View Slide

  13. Compose Multiplatform
    • Android (via Jetpack Compose)

    • Desktop (Windows, Mac OS, Linux)

    • Web (Experimental)

    View Slide

  14. Compose Multiplatform
    • Android (via Jetpack Compose)

    • Desktop (Windows, Mac OS, Linux)

    • Web (Experimental)

    • iOS (Alpha)

    View Slide

  15. Compose Multiplatform

    View Slide

  16. Compose Multiplatform

    View Slide

  17. UI Structure

    View Slide

  18. androidApp
    iOSApp
    desktopApp
    shared
    Structure

    View Slide

  19. shared
    src
    commonMain
    androidMain
    iOSMain
    Shared Module

    View Slide

  20. shared
    src
    commonMain
    androidMain
    iOSMain
    build.gradle.kts
    Shared Module

    View Slide

  21. plugins {

    kotlin("multiplatform")

    }

    val commonMain by getting {

    dependencies {

    implementation(compose.ui)

    implementation(compose.foundation)

    implementation(compose.material)

    implementation(compose.runtime)

    }

    }

    Shared Module

    View Slide

  22. plugins {

    kotlin("multiplatform")

    }

    val commonMain by getting {

    dependencies {

    implementation(compose.ui)

    implementation(compose.foundation)

    implementation(compose.material)

    implementation(compose.runtime)

    }

    }

    Shared Module

    View Slide

  23. shared
    src
    commonMain
    androidMain
    iOSMain
    Shared Module

    View Slide

  24. UI Structure
    App Root View
    Android iOS

    View Slide

  25. UI Structure
    App Root View
    Android iOS

    View Slide

  26. shared
    src
    commonMain
    androidMain
    iOSMain
    UI Structure

    View Slide

  27. shared
    src
    commonMain
    androidMain
    iOSMain
    ImagesApp.commmon.kt
    UI Structure

    View Slide

  28. fun ImagesAppCommon() {

    Scaffold(

    topBar = {

    TopAppBar(
    ...
    )

    },

    content = {

    ...


    }

    )

    }
    UI Structure

    View Slide

  29. fun ImagesAppCommon() {

    Scaffold(

    topBar = {

    TopAppBar(
    ...
    )

    },

    content = {

    ...


    }

    )

    }
    UI Structure

    View Slide

  30. shared
    src
    commonMain
    androidMain
    iOSMain
    ImagesApp.commmon.kt
    ImagesAppTheme.kt
    Shared Module

    View Slide

  31. App Theme
    val LightColorPalette = lightColors(
    ...
    )

    val DarkColorPalette = darkColors(
    ...
    )

    @Composable

    fun ImagesAppTheme(

    darkTheme: Boolean = isSystemInDarkTheme(),

    content: @Composable ()
    ->
    Unit

    ) {

    MaterialTheme(

    colors = if (darkTheme) DarkColorPalette else LightColorPalette,

    content = content

    )

    }

    View Slide

  32. App Theme
    val LightColorPalette = lightColors(
    ...
    )

    val DarkColorPalette = darkColors(
    ...
    )

    @Composable

    fun ImagesAppTheme(

    darkTheme: Boolean = isSystemInDarkTheme(),

    content: @Composable ()
    ->
    Unit

    ) {

    MaterialTheme(

    colors = if (darkTheme) DarkColorPalette else LightColorPalette,

    content = content

    )

    }

    View Slide

  33. shared
    src
    commonMain
    androidMain
    iOSMain
    ImagesApp.android.kt
    Shared Module

    View Slide

  34. UI Structure
    @Composable

    fun MainAndroid() {

    ImagesAppTheme {

    ImagesAppCommon()

    }

    }

    View Slide

  35. androidApp
    iOSApp
    desktopApp
    shared
    UI Structure

    View Slide

  36. UI Structure
    class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)



    setContent {

    MainAndroid()

    }

    }

    }

    View Slide

  37. UI Structure
    class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)



    setContent {

    MainAndroid()

    }

    }

    }

    View Slide

  38. shared
    src
    commonMain
    androidMain
    iOSMain
    ImagesApp.iOS.kt
    Shared Module

    View Slide

  39. UI Structure
    fun MainiOS(): UIViewController

    ComposeUIViewController {

    ImagesAppCommon()

    }

    View Slide

  40. UI Structure
    fun MainiOS(): UIViewController =

    ComposeUIViewController {

    ImagesAppCommon()

    }

    View Slide

  41. androidApp
    iOSApp
    desktopApp
    shared
    UI Structure

    View Slide

  42. UI Structure
    @main

    struct iOSApp: App {

    var body: some Scene {

    WindowGroup {

    ContentView()

    }

    }

    }

    View Slide

  43. UI Structure
    @main

    struct iOSApp: App {

    var body: some Scene {

    WindowGroup {

    ContentView()

    }

    }

    }

    View Slide

  44. UI Structure
    struct ComposeView: UIViewControllerRepresentable {

    func makeUIViewController(context: Context)
    ->
    UIViewController {

    let controller = Main_iosKt.MainiOS()

    return controller

    }

    }

    View Slide

  45. UI Structure
    struct ComposeView: UIViewControllerRepresentable {

    func makeUIViewController(context: Context)
    ->
    UIViewController {

    let controller = Main_iosKt.MainiOS()

    return controller

    }

    }

    View Slide

  46. UI Structure
    struct ComposeView: UIViewControllerRepresentable {

    func makeUIViewController(context: Context)
    ->
    UIViewController {

    let controller = Main_iosKt.MainiOS()

    return controller

    }

    }

    View Slide

  47. UI Structure
    struct ContentView: View {

    var body: some View {

    ZStack {

    ComposeView()

    ...

    }

    }

    }

    View Slide

  48. UI Structure
    struct ContentView: View {

    var body: some View {

    ZStack {

    ComposeView()

    ...

    }

    }

    }

    View Slide

  49. UI Structure
    struct ContentView: View {

    var body: some View {

    ZStack {

    ComposeView()

    ...

    }

    }

    }

    UIWindowScene
    UIWindow
    ComposeWindow
    SkikkoUIView
    View Hierarchy

    View Slide

  50. UI Structure
    struct ContentView: View {

    var body: some View {

    ZStack {

    ComposeView()

    ...

    }

    }

    }

    UIWindowScene
    UIWindow
    ComposeWindow
    SkikkoUIView
    View Hierarchy

    View Slide

  51. UI Structure
    struct ContentView: View {

    var body: some View {

    ZStack {

    ComposeView()

    ...

    }

    }

    }

    UIWindowScene
    UIWindow
    ComposeWindow
    SkikkoUIView
    View Hierarchy

    View Slide

  52. https:
    /
    /
    github.com/JetBrains/skiko
    Skiko

    View Slide

  53. UI Structure
    App Root View
    Android iOS

    View Slide

  54. UI Structure

    View Slide

  55. Challenge: Image Loading

    View Slide

  56. https:
    /
    /
    github.com/coil-kt/coil/issues/842
    Coil-kt

    View Slide

  57. shared
    src
    resource
    image1.jpeg
    image2.jpeg
    image3.jpeg
    Shared Module

    View Slide

  58. val commonMain by getting {

    dependencies {

    implementation(compose.components.resources)

    }

    }

    Shared Module

    View Slide

  59. val commonMain by getting {

    dependencies {

    implementation(compose.components.resources)

    }

    }

    Shared Module

    View Slide

  60. class ImageProvider {

    suspend fun getImageBitmap(picture: ImageData): ImageBitmap =

    resource(picture.url).readBytes().toImageBitmap()

    }

    Shared Module

    View Slide

  61. class ImageProvider {

    suspend fun getImageBitmap(picture: ImageData): ImageBitmap =

    resource(picture.url).readBytes()

    }

    Shared Module

    View Slide

  62. class ImageProvider {

    suspend fun getImageBitmap(picture: ImageData): ImageBitmap =

    resource(picture.url).readBytes().toImageBitmap()

    }

    Shared Module

    View Slide

  63. Except/Actual

    View Slide

  64. shared
    src
    commonMain
    androidMain
    Shared Module
    iOSMain

    View Slide

  65. expect fun ByteArray.toImageBitmap(): ImageBitmap

    Shared Module

    View Slide

  66. shared
    src
    commonMain
    androidMain
    Shared Module
    iOSMain

    View Slide

  67. actual fun ByteArray.toImageBitmap(): ImageBitmap = toAndroidBitmap().asImageBitmap()

    fun ByteArray.toAndroidBitmap(): Bitmap {

    return BitmapFactory.decodeByteArray(this, 0, size)

    }

    Shared Module

    View Slide

  68. actual fun ByteArray.toImageBitmap(): ImageBitmap = toAndroidBitmap().asImageBitmap()

    fun ByteArray.toAndroidBitmap(): Bitmap {

    return BitmapFactory.decodeByteArray(this, 0, size)

    }

    Shared Module

    View Slide

  69. actual fun ByteArray.toImageBitmap(): ImageBitmap = toAndroidBitmap().asImageBitmap()

    fun ByteArray.toAndroidBitmap(): Bitmap {

    return BitmapFactory.decodeByteArray(this, 0, size)

    }

    Shared Module

    View Slide

  70. actual fun ByteArray.toImageBitmap(): ImageBitmap = toAndroidBitmap().asImageBitmap()

    fun ByteArray.toAndroidBitmap(): Bitmap {

    return BitmapFactory.decodeByteArray(this, 0, size)

    }

    Shared Module

    View Slide

  71. shared
    src
    commonMain
    androidMain
    Shared Module
    iOSMain

    View Slide

  72. actual fun ByteArray.toImageBitmap(): ImageBitmap =

    Image.makeFromEncoded(this).toComposeImageBitmap()

    Shared Module

    View Slide

  73. actual fun ByteArray.toImageBitmap(): ImageBitmap =

    Image.makeFromEncoded(this).toComposeImageBitmap()

    Shared Module

    View Slide

  74. actual fun ByteArray.toImageBitmap(): ImageBitmap =

    Image.makeFromEncoded(this).toComposeImageBitmap()

    Shared Module

    View Slide

  75. class ImageProvider {

    suspend fun getImageBitmap(picture: ImageData): ImageBitmap =

    resource(picture.url).readBytes().toImageBitmap()

    }

    Shared Module

    View Slide

  76. UI Structure

    View Slide

  77. @Composable

    fun ImagesList(images: List) {

    Column {

    LazyVerticalGrid(

    columns = GridCells.Fixed(3)

    ) {

    items(images) {

    SplashImage(

    imageData = it

    )

    }

    }

    }

    }

    UI Structure

    View Slide

  78. @Composable

    fun ImagesList(images: List) {

    Column {

    LazyVerticalGrid(

    columns = GridCells.Fixed(3)

    ) {

    items(images) {

    SplashImage(

    imageData = it

    )

    }

    }

    }

    }

    UI Structure

    View Slide

  79. @Composable

    fun ImagesList(images: List) {

    Column {

    LazyVerticalGrid(

    columns = GridCells.Fixed(3)

    ) {

    items(images) {

    SplashImage(

    imageData = it

    )

    }

    }

    }

    }

    UI Structure

    View Slide

  80. @Composable

    fun SplashImage(imageData: ImageData) {

    val imageProvider = LocalImageProvider.current

    var imageBitmap by remember(imageData) {

    mutableStateOf(null)

    }

    LaunchedEffect(imageData) {

    imageBitmap = imageProvider.getImageBitmap(imageData)

    }

    imageBitmap
    ?.
    let {

    Image(

    bitmap = bitmap,

    ...


    )

    }

    }

    UI Structure

    View Slide

  81. @Composable

    fun SplashImage(imageData: ImageData) {

    val imageProvider = LocalImageProvider.current

    var imageBitmap by remember(imageData) {

    mutableStateOf(null)

    }

    LaunchedEffect(imageData) {

    imageBitmap = imageProvider.getImageBitmap(imageData)

    }

    imageBitmap
    ?.
    let {

    Image(

    bitmap = bitmap,

    ...


    )

    }

    }

    UI Structure

    View Slide

  82. @Composable

    fun SplashImage(imageData: ImageData) {

    val imageProvider = LocalImageProvider.current

    var imageBitmap by remember(imageData) {

    mutableStateOf(null)

    }

    LaunchedEffect(imageData) {

    imageBitmap = imageProvider.getImageBitmap(imageData)

    }

    imageBitmap
    ?.
    let {

    Image(

    bitmap = bitmap,

    ...


    )

    }

    }

    UI Structure

    View Slide

  83. @Composable

    fun SplashImage(imageData: ImageData) {

    val imageProvider = LocalImageProvider.current

    var imageBitmap by remember(imageData) {

    mutableStateOf(null)

    }

    LaunchedEffect(imageData) {

    imageBitmap = imageProvider.getImageBitmap(imageData)

    }

    imageBitmap
    ?.
    let {

    Image(

    bitmap = bitmap,

    ...


    )

    }

    }

    UI Structure

    View Slide

  84. UI Structure

    View Slide

  85. UI Structure
    App Root View
    Android
    iOS
    Images List
    Images Details
    Shared

    View Slide

  86. Challenges: App Architecture, Navigation

    View Slide

  87. Compose UI
    View Model
    UI State
    Events

    View Slide

  88. UI Structure
    App Root View
    Android
    iOS
    Images List
    Images Details
    Shared

    View Slide

  89. UI Structure
    App Root View
    Android
    iOS
    Images List
    Images Details
    Shared
    View Model

    View Slide

  90. Shared Module
    shared
    src
    commonMain
    androidMain
    iOSMain

    View Slide

  91. Shared Module
    shared
    src
    commonMain
    androidMain
    iOSMain
    ImagesListViewModel.kt

    View Slide

  92. App Architecture
    sealed class ImagesListUiState {

    object Loading: ImagesListUiState()

    data class Success(

    val images: List

    ): ImagesListUiState()

    data class Error(

    val errorMessage: String

    ): ImagesListUiState()

    }

    View Slide

  93. App Architecture
    sealed class ImagesListUiState {

    object Loading: ImagesListUiState()

    data class Success(

    val images: List

    ): ImagesListUiState()

    data class Error(

    val errorMessage: String

    ): ImagesListUiState()

    }

    View Slide

  94. App Architecture
    sealed class ImagesListUiState {

    object Loading: ImagesListUiState()

    data class Success(

    val images: List

    ): ImagesListUiState()

    data class Error(

    val errorMessage: String

    ): ImagesListUiState()

    }

    View Slide

  95. App Architecture
    sealed class ImagesListUiState {

    object Loading: ImagesListUiState()

    data class Success(

    val images: List

    ): ImagesListUiState()

    data class Error(

    val errorMessage: String

    ): ImagesListUiState()

    }

    View Slide

  96. App Architecture
    class ImagesListViewModel {

    init {
    ...
    }

    val state = MutableStateFlow(ImagesListUiState.Loading)
    val viewModelScope = CoroutineScope(Dispatchers.Main)
    }
    init }
    {

    View Slide

  97. App Architecture
    class ImagesListViewModel {

    init {
    ...
    }

    }
    init
    }
    {
    viewModelScope.launch(Dispatchers.Main) {

    try {

    val imagesList = imagesRepository.getImages()

    state.emit(uiState.Success(images = imagesList))

    } catch (e: Exception) {

    state.emit(uiState.Error("Something went wrong"))

    }

    }

    View Slide

  98. App Architecture
    @Composable

    fun ImagesAppCommon() {

    Scaffold(

    topBar = {
    ...
    },

    content = {

    }

    )

    }

    View Slide

  99. App Architecture
    @Composable

    fun ImagesAppCommon() {

    Scaffold(

    topBar = {
    ...
    },

    content = {

    }

    )

    }
    val uiState by viewModel.state.collectAsState()

    ImagesListScreen(uiState)

    View Slide

  100. App Architecture
    @Composable

    fun ImagesListScreen(uiState: UIState) {

    }

    }

    View Slide

  101. App Architecture
    @Composable

    fun ImagesListScreen(uiState: UIState) {

    when (uiState) {

    ImagesListUiState.Loading
    ->


    is ImagesListUiState.Success
    ->


    is ImagesListUiState.Error
    ->


    }

    }

    View Slide

  102. App Architecture
    @Composable

    fun ImagesListScreen(uiState: UIState) {

    when (uiState) {

    ImagesListUiState.Loading
    ->
    CircularProgressIndicator()

    is ImagesListUiState.Success
    ->


    is ImagesListUiState.Error
    ->


    }

    }

    View Slide

  103. App Architecture
    @Composable

    fun ImagesListScreen(uiState: UIState) {

    when (uiState) {

    ImagesListUiState.Loading
    ->
    CircularProgressIndicator()

    is ImagesListUiState.Success
    ->
    ImagesList(uiState.images)

    is ImagesListUiState.Error
    ->


    }

    }

    View Slide

  104. Navigation
    App Root View
    List Details

    View Slide

  105. App Architecture
    sealed class Screen {

    }

    View Slide

  106. App Architecture
    sealed class Screen {

    object List : Screen()

    }

    View Slide

  107. App Architecture
    sealed class Screen {

    object List : Screen()

    data class Details(val imageId: String) : Screen()

    }

    View Slide

  108. App Architecture
    @Composable

    fun ImagesAppCommon() {

    var screenState by remember { mutableStateOf(Screen.List) }

    when (val screen = screenState) {

    is Screen.List
    ->


    List(

    onItemClick = { screenState = Screen.Details(imageId = it) }

    )

    is Screen.Details
    ->


    Details(

    text = screen.text,

    onBack = { screenState = Screen.List }

    )

    }

    }

    View Slide

  109. App Architecture
    @Composable

    fun ImagesAppCommon() {

    var screenState by remember { mutableStateOf(Screen.List) }

    when (val screen = screenState) {

    is Screen.List
    ->


    List(

    onItemClick = { screenState = Screen.Details(imageId = it) }

    )

    is Screen.Details
    ->


    Details(

    text = screen.text,

    onBack = { screenState = Screen.List }

    )

    }

    }

    View Slide

  110. App Architecture
    @Composable

    fun ImagesAppCommon() {

    var screenState by remember { mutableStateOf(Screen.List) }

    when (val screen = screenState) {

    is Screen.List
    ->


    List(

    onItemClick = { screenState = Screen.Details(imageId = it) }

    )

    is Screen.Details
    ->


    Details(

    text = screen.text,

    onBack = { screenState = Screen.List }

    )

    }

    }

    View Slide

  111. App Architecture
    @Composable

    fun ImagesAppCommon() {

    var screenState by remember { mutableStateOf(Screen.List) }

    when (val screen = screenState) {

    is Screen.List
    ->


    List(

    onItemClick = { screenState = Screen.Details(imageId = it) }

    )

    is Screen.Details
    ->


    Details(

    text = screen.imageId,

    onBack = { screenState = Screen.List }

    )

    }

    }

    View Slide

  112. App Architecture
    ● Lifecycle Aware

    ● Navigation

    View Slide

  113. https:
    /
    /
    github.com/arkivanov/Decompose
    Decompose

    View Slide

  114. Decompose
    ● Lifecycle Aware Components

    ● Back stack management

    View Slide

  115. Decompose
    interface ListComponent {

    val uiState: Value

    fun onImageClicked(imageId: String)

    data class UiState(

    val images: List,

    )

    }

    View Slide

  116. Decompose
    interface ListComponent {

    val uiState: Value

    fun onImageClicked(imageId: String)

    data class UiState(

    val images: List,

    )

    }

    View Slide

  117. Decompose
    interface ListComponent {

    val uiState: Value

    fun onImageClicked(imageId: String)

    data class UiState(

    val images: List,

    )

    }

    View Slide

  118. Decompose
    class ImagesListComponent(

    componentContext: ComponentContext,

    val onImageSelected: (imageId: String)
    ->
    Unit,

    ) : ListComponent {

    override val uiState: Value =

    MutableValue(UiState.Loading)

    override fun onItemClicked(imageId: String) {

    onImageSelected(imageId)

    }

    }

    View Slide

  119. Decompose
    class ImagesListComponent(

    componentContext: ComponentContext,

    val onImageSelected: (imageId: String)
    ->
    Unit,

    ) : ListComponent {

    override val uiState: Value =

    MutableValue(UiState.Loading)

    override fun onItemClicked(imageId: String) {

    onImageSelected(imageId)

    }

    }

    View Slide

  120. Decompose
    class ImagesListComponent(

    componentContext: ComponentContext,

    val onImageSelected: (imageId: String)
    ->
    Unit,

    ) : ListComponent {

    override val uiState: Value =

    MutableValue(UiState.Loading)

    override fun onItemClicked(imageId: String) {

    onImageSelected(imageId)

    }

    }

    View Slide

  121. @Composable

    fun ImagesList(

    component: ListComponent,

    images: List,

    onImageClicked: (Int)
    ->
    Unit

    ) {


    val uiState by component.uiState.subscribeAsState()

    }

    Decompose

    View Slide

  122. @Composable

    fun ImagesList(

    component: ListComponent,

    images: List,

    onImageClicked: (Int)
    ->
    Unit

    ) {


    val uiState by component.uiState.subscribeAsState()

    }

    Decompose

    View Slide

  123. Navigation
    App Root View
    List Details

    View Slide

  124. interface RootComponent {

    val stack: Value<*
    , Child
    >>


    sealed class Child {

    class ListChild(val component: ListComponent) : Child()

    class DetailsChild(val component: DetailsComponent) : Child()

    }

    }
    App Architecture

    View Slide

  125. interface RootComponent {

    val stack: Value<*
    , Child
    >>


    sealed class Child {

    class ListChild(val component: ListComponent) : Child()

    class DetailsChild(val component: DetailsComponent) : Child()

    }

    }
    App Architecture

    View Slide

  126. interface RootComponent {

    val stack: Value<*
    , Child
    >>


    sealed class Child {

    class ListChild(val component: ListComponent) : Child()

    class DetailsChild(val component: DetailsComponent) : Child()

    }

    }
    App Architecture

    View Slide

  127. class DefaultRootComponent(
    ...
    ): RootComponent {

    @Parcelize

    sealed interface Config : Parcelable {

    object List : Config

    data class Details(val item: String) : Config

    }

    }
    App Architecture

    View Slide

  128. class DefaultRootComponent(
    ...
    ): RootComponent {


    val navigation = StackNavigation()

    @Parcelize

    sealed interface Config : Parcelable {

    object List : Config

    data class Details(val item: String) : Config

    }

    }
    App Architecture

    View Slide

  129. class DefaultRootComponent(
    ...
    ): RootComponent {


    val navigation = StackNavigation()

    @Parcelize

    sealed interface Config : Parcelable {

    object List : Config

    data class Details(val item: String) : Config

    }

    }
    App Architecture
    val stack =

    childStack(

    source = navigation,

    initialConfiguration = Config.List,

    handleBackButton = true,

    childFactory =
    ::
    child,

    )

    View Slide

  130. class DefaultRootComponent(
    ...
    ): RootComponent {


    val navigation = StackNavigation()

    @Parcelize

    sealed interface Config : Parcelable {

    object List : Config

    data class Details(val item: String) : Config

    }

    }
    App Architecture

    View Slide

  131. class DefaultRootComponent(
    ...
    ): RootComponent {

    }
    App Architecture
    fun listComponent(): ListComponent =

    ImagesListComponent(

    onItemSelected = { imageId: String
    ->


    navigation.push(Config.Details(item = imageId))

    },

    )

    View Slide

  132. class DefaultRootComponent(
    ...
    ): RootComponent {

    }
    App Architecture
    fun listComponent(): ListComponent =

    ImagesListComponent(

    onItemSelected = { imageId: String
    ->


    navigation.push(Config.Details(item = imageId))

    },

    )

    View Slide

  133. class DefaultRootComponent(
    ...
    ): RootComponent {

    }
    App Architecture
    fun detailsComponent(): DetailsComponent =

    ImageDetailsComponent(

    image = config.image,

    onFinished = navigation
    ::
    pop,

    )

    View Slide

  134. class DefaultRootComponent(
    ...
    ): RootComponent {

    }
    App Architecture
    fun detailsComponent(): DetailsComponent =

    ImageDetailsComponent(

    image = config.image,

    onFinished = navigation
    ::
    pop,

    )

    View Slide

  135. Navigation
    App Root View
    List Details

    View Slide

  136. @Composable

    fun ImagesAppCommon(component: RootComponent) {

    Children(

    stack = component.stack

    ) {

    when (val child = it.instance) {

    is ListChild
    ->
    ListContent(component = child.component)

    is DetailsChild
    ->
    DetailsContent(component = child.component)

    }

    }

    }

    App Architecture

    View Slide

  137. @Composable

    fun ImagesAppCommon(component: RootComponent) {

    Children(

    stack = component.stack

    ) {

    when (val child = it.instance) {

    is ListChild
    ->
    ListContent(component = child.component)

    is DetailsChild
    ->
    DetailsContent(component = child.component)

    }

    }

    }

    App Architecture

    View Slide

  138. class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)

    val root =

    DefaultRootComponent(

    componentContext = defaultComponentContext(),

    )

    setContent {

    MaterialTheme {

    Surface {

    RootContent(component = root, modifier = Modifier.fillMaxSize())

    }

    }

    }

    App Architecture

    View Slide

  139. class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)

    val root =

    DefaultRootComponent(

    componentContext = defaultComponentContext(),

    )

    setContent {

    MaterialTheme {

    Surface {

    RootContent(component = root, modifier = Modifier.fillMaxSize())

    }

    }

    }

    App Architecture

    View Slide

  140. class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)

    val root =

    DefaultRootComponent(

    componentContext = defaultComponentContext(),

    )

    setContent {

    MaterialTheme {

    Surface {

    ImageAppCommon(component = root)

    }

    }

    }

    App Architecture

    View Slide

  141. class AppDelegate: NSObject, UIApplicationDelegate {

    let rootHolder: RootHolder = RootHolder()

    }

    App Architecture

    View Slide

  142. @main

    struct app_iosApp: App {

    var rootHolder: RootHolder { appDelegate.rootHolder }

    var body: some Scene {

    WindowGroup {

    ComposeView(rootHolder.root)

    }

    }

    }

    App Architecture

    View Slide

  143. Decompose
    ● Lifecycle Aware Components

    ● Back stack management

    View Slide

  144. Building Multiplatform Apps with Compose
    ● Setup Project

    ● Share Compose UI

    ● SwiftUI & Compose Interop

    ● Architecture & Navigation

    View Slide

  145. Thank You!
    www.codingwithmohit.com
    @[email protected]

    View Slide