skip to content
back to blog

Trying out the Multiplatform Compose

blog

Jetpack Compose

I started Android development recently enough to learn Jetpack Compose as my main UI framework. The Android developers I heard from who had used XML praised how much simpler Compose made their work.

Consider a screen with loading, success, and error states. In XML-based development, I would usually create separate XML files for those states. With Compose and Kotlin, I can describe them together. First, define the model and screen state:

// data model
data class DataItem(
    val name: String,
    val checked: Boolean = false
)
 
// screen state model
sealed class ScreenState(
    val items: List<DataItem>,
    val title: String?
) {
    object Success : ScreenState(
        items = listOf(DataItem("item 1"), DataItem("item 2")),
        title = "Fetch Successful"
    )
 
    object Failure : ScreenState(
        items = listOf(),
        title = "Fetch Failed"
    )
 
    object Loading : ScreenState(
        items = listOf(),
        title = "Loading..."
    )
}

Then define the screen:

@Composable
fun MainScreen(state: ScreenState) {
    Column(
        horizontalAlignment = Alignment.Center,
        verticalArrangement = Arrangement.spacedBy(4.dp)
    ) {
        when (state) {
            is ScreenState.Success -> {
                state.items.forEach { item ->
                    ListItem {
                        Checkbox(
                            selected = item.checked,
                            onCheckedChange = { /* Checkbox logic here */ }
                        )
                    }
                }
            }
            is ScreenState.Failure -> {
                Text(state.title)
                Icon(
                    Icons.Default.Warning,
                    contentDescription = "Error"
                )
                Button(
                    onClick = { /* Handling logic here */ }
                ) {
                    Text("Try again")
                }
            }
            is ScreenState.Loading -> {
                CircularProgressIndicator(
                    modifier = Modifier.size(64.dp)
                )
                Text(state.title)
            }
        }
    }
}

The screen's logic and UI live in one file. I can describe what each state should look like, and the code is easy to follow without much setup.

Sharing code across platforms

Native development is usually a good choice for performance. Frameworks that share code across iOS, Android, and desktop can save development time. React Native (RN)1 and Flutter2 have been the most established options.

React Native is the most popular of these frameworks, helped by its large collection of libraries and the familiar starting point for React and JavaScript developers. It also supports sending code updates without an app store review for each release. That makes it useful for early products with few complicated native features. Expo3 has helped it become more widely used.

JetBrains' Kotlin Multiplatform (KMM)4 lets developers share code across more of an application, including a backend built with Ktor. The server and apps can keep shared application rules and screen logic in one repository, reducing the work of implementing the same behavior for each platform.

Compose Multiplatform builds on Jetpack Compose and lets those apps share UI code too. That makes it an alternative to React Native. At the time of this experiment, Compose for iOS had just entered beta and was not fully stable. I expected those issues to improve as KMM developed.

Trying KMM

Kotlin Multiplatform Wizard5 provides project templates and lets you choose a shared Compose UI for iOS; otherwise, it defaults to SwiftUI. A generated project has this structure:

.
├── composeApp
│   ├── build.gradle.kts
│   └── src
│       ├── androidMain
│       │   ├── AndroidManifest.xml
│       │   ├── kotlin
│       │   └── res
│       ├── commonMain
│       │   ├── composeResources
│       │   └── kotlin
│       └── iosMain
│           └── kotlin
├── iosApp
│   ├── Configuration
│   ├── iosApp
│   └── iosApp.xcodeproj
├── gradle
│   ├── libs.versions.toml
│   └── wrapper
├── local.properties
├── gradle.properties
├── build.gradle.kts
└── settings.gradle.kts

At the root are composeApp and iosApp. The iosApp directory holds the Xcode resources needed to build the iOS app. The shared code is in composeApp/commonMain.

Footnotes

  1. https://reactnative.dev ↩

  2. https://flutter.dev ↩

  3. https://expo.dev ↩

  4. https://kotlinlang.org/docs/multiplatform.html ↩

  5. https://kmp.jetbrains.com ↩

comments