Skip to Content
Course content

113: Sharing UI with Compose Multiplatform

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Where do I actually put my UI code so it runs everywhere?

The magic happens in the commonMain source set. If you've been following along with the previous lessons on Kotlin Multiplatform, you know this is where your business logic lives. With Compose Multiplatform, this is also where your UI lives. Instead of writing a XML layout for Android and a SwiftUI view for iOS, you write a @Composable function once, and it gets rendered by the Compose runtime on both platforms.

Let's say we're building a simple User Profile card. I'd create a shared Composable in commonMain like this:

@Composable
fun UserProfileCard(name: String, bio: String) {
    Card(modifier = Modifier.padding(16.dp).fillMaxWidth()) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(text = name, style = MaterialTheme.typography.h6)
            Spacer(modifier = Modifier.height(8.dp))
            Text(text = bio, style = MaterialTheme.typography.body2)
        }
    }
}

Now, you just call UserProfileCard from your Android MainActivity and your iOS MainViewController. I've found that the biggest mental shift is realizing that the UI is no longer a "platform detail"β€”it's now part of your core application logic.

What if I need a component to look or behave differently on iOS?

You'll eventually hit a wall where a "one size fits all" approach feels wrong. Maybe you want a specific padding for the iOS notch, or you want to use a native-looking toggle that behaves differently than the Android version. This is where the expect/actual pattern comes back into play, but applied to UI.

I usually handle this by defining an "expected" Composable in commonMain and providing "actual" implementations in androidMain and iosMain. Here is how I'd handle a platform-specific header:

// In commonMain
@Composable
expect fun PlatformHeader(title: String)

// In androidMain
@Composable
actual fun PlatformHeader(title: String) {
    Text(text = title, modifier = Modifier.background(Color.Blue).fillMaxWidth())
}

// In iosMain
@Composable
actual fun PlatformHeader(title: String) {
    Text(text = title, modifier = Modifier.background(Color.Gray).fillMaxWidth())
}

Keep these expect functions lean. If you find yourself duplicating 90% of the UI in both platforms, you're doing it wrong. Wrap the shared parts in a common Composable and only use expect/actual for the tiny slices that actually need to differ.

How do I handle images and strings without writing platform-specific paths?

Handling resources used to be the most annoying part of KMP, but the composeResources library has mostly solved this. You no longer have to manually manage res/drawable on Android and Assets.xcassets on iOS. Instead, you put everything in a commonMain/composeResources folder.

Once you've added your image (say, profile_placeholder.xml) to the resources folder, the Gradle plugin generates a Res object for you. You can use it directly in your shared code:

@Composable
fun ProfileImage() {
    Image(
        painter = painterResource(Res.drawable.profile_placeholder),
        contentDescription = "User Profile Picture"
    )
}

It's honestly a relief. I spent way too many hours in the past syncing filenames between Android and Xcode. Now, if the file is in the common resources folder, it's available everywhere. Just remember to run your build once after adding a new resource so the Res class can be generated.




πŸ“‹ Practical Task

Build a Cross-Platform Theme Switcher

Your goal is to create a shared UI component that demonstrates both shared and platform-specific logic. Build a ThemeToggle component in commonMain with the following requirements:

  • Create a shared @Composable function that displays a Switch and a Text label ("Dark Mode").
  • Use an expect function called PlatformBrandLabel() to display a small text label at the bottom of the toggle.
  • Implement the actual version of PlatformBrandLabel to return "Android Edition" in androidMain and "iOS Edition" in iosMain.
  • Ensure the ThemeToggle is wrapped in a Card for a polished look.

Verify that when you run the app on both an Android emulator and an iOS simulator, the toggle works identically, but the brand label correctly identifies the platform.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.