Kotlin multiplatform, learn by creating, Episode 5, Create UI with Compose

Ahmed Nabil
6 min readNov 9, 2023

--

Salam my friend, thank you for staying with me during this journey, in the previous episode, we explored the project, opened the iOS xcode project and knew about expect/actual pattern.

Today, we are going to create simple UI for our Breezy app, using compose multiplatform, you can download the source code from the link below. So, let’s get started.

Compose multiplatform

A declarative UI framework developed by Jetbrains and open-source contributors. based mainly on Google’s Jetpack Compose.

Declarative means you define what you want, while imperative you define how to achieve that UI, which includes defining the elements, linking it to classes, update it manually every time data changes.

Composable Functions

Functions annotated with @Composable, you define what you need to draw, and the compose compiler will translate this into UI.

in our project, go to MainActivity.kt, and notice the following code:

@Composable
fun GreetingView(text: String) {
Text(text = text)
}

@Preview
@Composable
fun DefaultPreview() {
MyApplicationTheme {
GreetingView("Hello, Android!")
}
}

The GreetingView() is a function that takes a String parameter and draws a Text with it, the Text() is a composable function itself. If you want to preview the UI without the need to build and run the app, you can use the @Preview annotation, and from the top right, you can click on the menu shown below inside the yellow circle, to toggle the view and see the preview. you can even view this preview in interactive mode if you define a click action for example and see that it does when you click on it, etc.

Now, we will start building our UI for android using Jetpack Compose, then will use it to run on other platforms using Compose Multiplatform, so let’s start.

Right-click on the GreetingView function and select: Refactor->Rename, or use the associated keyboard shortcut (Shift+F6). we will rename it to WeatherView.
The simplest view should contain city name, temperature, measure unit, so let’s create our UI model, In the commonMain module, create a new data class as follows:

package com.ahmednmahran.breezy

data class WeatherUIModel(
val unit: Unit,
val temperature: String,
val city: String,
)

enum class Unit(val symbol: String) {
CELSIUS("°C"), FAHRENHEIT("°F")
}

Now, replace your WeatherView fun with the following:

@Composable
fun WeatherView(model: WeatherUIModel) {
Column {
Text(text = model.city)
Row {
Text(text = model.temperature.toString())
Text(text = model.unit.symbol)
}
}
}

Column and Row are composable functions to align composables vertically and horizontally.

Now, your compiler will complain because WeatherView is called with String parameter, so let’s create a WeatherUIModel instance and update our code, it should now look like this:

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
WeatherView(WeatherUIModel(Unit.CELSIUS, "23", "Cairo"))
...
}
}
@Preview
@Composable
fun DefaultPreview() {
MyApplicationTheme {
WeatherView(WeatherUIModel(Unit.CELSIUS, "23", "Cairo"))
}
}

Refresh you preview, or run the app on android, and it should look like this:

But, this looks too small, so let’s modify the UI properties to look better.

Now, edit the WeatherView function to look like the following:

@Composable
fun WeatherView(model: WeatherUIModel) {
Column(
modifier = Modifier
.padding(16.dp) // padding around the column
.fillMaxWidth(), // fill the available space
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = model.city,
style = MaterialTheme.typography.headlineLarge
)
Spacer(modifier = Modifier.height(32.dp))
Row {
Text(text = model.temperature,
style = TextStyle(fontWeight = FontWeight.Bold,
fontSize = 64.sp))
Text(text = model.unit.symbol)
}
}
}

Modifier

Compose uses modifiers. They allow you to change the composable’s size, layout, appearance or add high-level interactions, such as making an element clickable. You can chain them to create richer composables

Typography

You can see that we styled the text with our custom styles, like in the temperature text, but we can also use the built-in Material typography as we did in the city text.

Try playing with these values to learn more about typography and modifiers.

The final result now should look like the following:

Use Compose Multiplatform on iOS

To use the Compose MP on iOS and other targets, we can add the dependencies manually, or can use the online wizard to create a starter project, we will show here the manual way since we already created our project from scratch

  • In the libs.version.toml file:

update the [versions] to look like this:

[versions]
agp = "8.1.3"
kotlin = "1.9.20"
compose = "1.5.4"
compose-plugin = "1.5.10"
compose-compiler = "1.5.4"
compose-material3 = "1.1.2"
androidx-activityCompose = "1.8.0"

update [plugins] to include the compose multiplatform plugin

[plugins]
jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }
#...
  • in the root project’s build.gradle.kts add the following line inside the plugins:
alias(libs.plugins.jetbrainsCompose) apply false
  • in shared module’s build.gradle.kts:
import org.jetbrains.compose.ExperimentalComposeLibrary

plugins {
...
alias(libs.plugins.jetbrainsCompose) // add this line
}

kotlin {
// remove this:
// targetHierarchy.default()
// add this:
applyDefaultHierarchyTemplate()
...
  • under this line
sourceSets {
val commonMain by getting {
dependencies {
//put your multiplatform dependencies here

add these dependencies

implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
@OptIn(ExperimentalComposeLibrary::class)
implementation(compose.components.resources)

Now, sync the gradle project.

Edit the code

  • move the MyApplicationTheme.kt file under the shared module, we will rename it to BreezyTheme
  • create a file in the same place and call it App.kt, we will use the composables created earlier in MainActivity.kt inside App.kt,

@Composable
fun App() {
BreezyTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
WeatherView(WeatherUIModel(Unit.CELSIUS, "23", "Cairo"))
}
}
}

@Composable
fun WeatherView() {...}

now edit MainActivity.kt

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
App()
}
}
}


@Preview
@Composable
fun DefaultPreview() {
App()
}

In iosMain module, put this file: MainViewController.kt

package com.ahmednmahran.breezy

import androidx.compose.ui.window.ComposeUIViewController

fun MainViewController() = ComposeUIViewController { App() }

in iosApp module, replace the content of ContentView.swift with this:

import UIKit
import SwiftUI
import ComposeApp

struct ComposeView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
MainViewControllerKt.MainViewController()
}

func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}

struct ContentView: View {
var body: some View {
ComposeView()
.ignoresSafeArea(.keyboard) // Compose has own keyboard handler
}
}

now, from android studio, run the iosApp as mentioned in the previous article, you should see the app running on iOS like this:

Congratulations 🎉 🎉 🎉, you have now made your compose code run on iOS too.

You can find the full code for this article on the github project, choose the ep5-weather_view branch, and compare with what you’ve written.

please feel free to ask any questions in the comments below.

In the next episodes inshaa’ Allah we will enhance our UI, add more targets, and use some weather apis to show real data on our app. so stay tuned !

Please follow me, and take some time to write me your thoughts in the comments, and let’s connect through my links in the bio or simply click here or scan this QR below to go to my youtube channel’s about page listing all my links:

--

--

Ahmed Nabil

#Kotlin Lover, Android Engineer, Public speaker, Founder of @KotlinEgypt https://linktr.ee/AhmedNMahran