Getting Started with Compose Multiplatform

October 25, 2023 | Tech Talk

Hello. I'm an Android app developer on the Wadiz app development team.

Last July, Wadizdeveloped a benefits page. At that time, we introduced Server Driven UI to the 'Welcome Coupon' issuance screen. Server Driven UI is a mechanism that allows the client app to dynamically render screens when UI design information is provided from the server. This enables UI changes without redeploying the app.

Compose Multiplatform

Example JSON used in Server-Driven UI

Thanks to this, we can now operate the 'Welcome Coupon' page more conveniently and smoothly! However, writing and modifying JSON every time was a complex and difficult task. We needed to develop an admin tool so that anyone managing the page could easily handle the UI. We introduced and developed using Compose Multiplatform. Here's a brief overview of that experience.

 

Compose Multiplatform

Compose Multiplatform is a UI framework provided by JetBrains. It's a tool that allows developers to simultaneously build desktop and mobile apps using Kotlin and Compose, which are familiar to Android developers. While it includes some experimental features, it supports iOS, Android, Desktop, and Web.

To begin iOS app development, you'll need macOS, Java, Android Studio, Xcode, and CocoaPods. Install and run KDoctor to verify everything is set up correctly as shown below. Additionally, installing the Kotlin Multiplatform Mobile plugin is recommended.

Compose Multiplatform

 

Getting Started with Compose Multiplatform

Compose Multiplatform

IntelliJ IDEA

First, you'll need IntelliJ IDEA to get started. Select New Project → Compose Multiplatform, then enter the project name, save location, package name, and other details.

Configuration

  • Single platform: Choose this when supporting a single platform—desktop or web—is sufficient.
  • Multiple platform: Choose this when you need support for multiple platforms such as desktop, mobile, and web (Experimental).

Let's take a look at the project's packages.

Compose Multiplatform

The project is structured with a common module and platform-specific modules. The common module contains platform-independent code. Platform-dependent code, such as that for Android or desktop, is written in the respective platform modules.

  • common

    • commonMain: Platform-independent code

    • androidMain: Android actual function, class implementation

    • desktopMain: desktop actual function, class creation

  • android: Android platform-dependent code

    • AndroidManifest.xml

  • desktop: desktop platform-specific code

In this project, most of the code could be written in the commonMain package. This is because the green screen, built using Compose, is platform-independent.

However, certain features—such as image loading, setting file save paths, and displaying dialogs—differ across platforms due to variations in the libraries and environments used. To separate code according to platform and manage it efficiently, Compose Multiplatform utilizes the keywords expect and actual.

Compose Multiplatform

By specifying expect functions or classes in the common module using the same concept as Java interfaces, implementation functions or classes are written by attaching actual to each platform (Android, desktop). Adding libraries is also done identically by specifying them in build.gradle for each platform.

Compose Multiplatform

serialization-json Since the library is platform-independent, commonMainI added it. material3 The libraries require different implementations for Android and desktop platforms. Therefore, we added them separately for each platform. As a result, we can now efficiently manage the necessary libraries across multiple platforms.

Android Material 3

androidx.compose.material3:material3:1.0.1

desktop material3

org.jetbrains.compose.material3:material3-desktop:1.3.0

 

Try writing some code

Next, I created a feature to add UI components to the screen. It displays a list. LazyRow Creates a Composable function to display the name and image for each option.

Compose Multiplatform

@Composable 
private fun ComponentOptionItems(
    clickAction: (ServerComponentType) -> Unit 
) { 
    Spacer(modifier = Modifier.background(Color.Black).fillMaxWidth().height(1.dp)) 
    LazyRow(
        modifier = Modifier.fillMaxWidth() 
            .height(200.dp)
            .background(Color.White), 
        horizontalArrangement = Arrangement.spacedBy(12.dp) 
    ) { 
        val typeList = ServerComponentType.values()
        items(typeList) {
            Column( 
                modifier = Modifier.fillMaxHeight().clickable { 
                    clickAction.invoke(it)
                }, 
                horizontalAlignment = Alignment.CenterHorizontally 
            ) { 
                Text( 
                    modifier = Modifier.padding(16.dp),
                    text = it.name, 
                    color = Color.Black, 
                    fontWeight = FontWeight.Bold 
                ) 
                getPainterFile(it.name.lowercase())?.let { painter -> 
                    Image(
                        painter = painter, 
                        contentDescription = it.name 
                    ) 
                } 
            } 
        } 
    } 
}

Compose Multiplatform

var openDialog: Pair<ServerComponentType, Component?>? by remember { 
  mutableStateOf(null) 
} 

showOptionSettingDialog(openDialog?.first, openDialog?.second, deleteAction = { ... }) 

ComponentOptionItems { 
  openDialog = Pair(it, null) 
}

openDialogcreate a variable named remember, ComponentOptionItems Changes the openDialog when the item is clicked. showOptionSettingDialogwhere openDialog It is being redrawn because the state has changed.

The method for displaying dialogs differs between Android and desktop. Therefore, we define an expect function and write an actual function tailored to each platform.

// common 
@Composable 
expect fun showOptionSettingDialog( 
  type: ServerComponentType?, 
  baseComponent: Component?, 
  deleteAction: () -> Unit, dismissAction: (Component?) -> Unit 
  ) 

// android 
@Composable 
actual fun showOptionSettingDialog( 
  type: ServerComponentType?, 
  baseComponent: Component?, 
  deleteAction: () -> Unit, 
  dismissAction: (Component?) -> Unit 
  ) { 
    if (type != null) { 
      var component by remember { mutableStateOf(baseComponent) } 
      Dialog(
        onDismissRequest = { dismissAction.invoke(null)},
        properties = DialogProperties(usePlatformDefaultWidth = false), 
        content = { 
            ... // 옵션 화면 그리기 
        } 
      ) 
    } 
}

// desktop 
@Composable 
actual fun showOptionSettingDialog( 
  type: ServerComponentType?, 
  baseComponent: Component?, 
  deleteAction: () -> Unit, 
  dismissAction: (Component?) -> Unit 
  ) { 
    if (type != null) {
      var component by remember { mutableStateOf(baseComponent) } 
      Dialog( 
        title = type.name, 
        visible = true, 
        state = DialogState(size = DpSize(1200.dp, 800.dp)), 
        onCloseRequest = { 
            dismissAction.invoke(null) 
        }) { 
          ... // 옵션 화면 그리기 
        } 
    } 
}

At first glance, both Android and desktop call the Dialog composable function, but they are different functions.

// android 
androidx.compose.ui:ui AndroidDialog.android 

// desktop 
org.jetbrains.compose.ui:ui-desktop Dialog.desktop

The option screens drawn in each dialog are identical across platforms. Therefore, we call the Composable function defined in common for shared use.

Compose Multiplatform

Desktop app

Compose Multiplatform

Android app

 

The results developed using Compose Multiplatform can run on both desktop and Android apps. You can also configure Server Driven UI screens to preview them. Extracting JSON data and saving it as a file is also possible.

Previously, it was a hassle to create JSON and test it on each platform. It was very convenient to be able to check the UI with the admin tool without that process.

Compose Multiplatform

 

 

In closing

Developing a desktop app seemed daunting at first, but I was also excited by the prospect of a new challenge. Being able to utilize Compose as well made me feel like I'd killed two birds with one stone after development.

However, there are still a few areas for improvement. Feedback suggested that a drawer navigation system, allowing modifications on a single screen rather than through dialogs, would be more convenient. We plan to improve the UI/UX based on this input.
Additionally, to make it easier for operations staff to use, we intend to replace sections written in development terminology like lineHeight, gravity, and margin with terms that are easier for anyone to understand.

I'm excited about the endless possibilities with Server Driven UI and Compose Multiplatform going forward. I'm also curious about the features that will be updated from the experimental features. It was a pleasure to gain such meaningful experience. 🙂🚀

 

 

Still have questions? 👀

Curious about the app development team's culture?👉 Click
The app development team also built Instagram sharing!👉 Click
See our open developer positions 👉 Click

👇 Click the tag to see posts with the same keyword.

Tech Talk

Tech Talk

Technical Organization

We create essential services that empower everyone to take on challenges and experience something new.