Sharing Compose components between Android and Desktop
Tags: compose, android, desktop, multiplatform
Published: 2021-04-27 10:03:13

---
title: Sharing Compose components between Android and Desktop
published: true
description: How you can build a KMP project with Compose for Android and Desktop and share components between them
tags: compose, android, desktop, multiplatform
---

Jetpack Compose is a declarative framework UI developed by Google and dedicated for Android apps. It is an incredible improvement for Android ecosystem and give us the opportunity to create interfaces with a modern approach.

But Jetbrains are working on Compose too. They created Compose for Desktop, still in alpha version, and we can see first implementations for Compose for Web. It isn't impossible that Compose will be compatible with more platforms in the future, like iOS.

In this article, we'll see how we can build a kotlin multiplatform project (aka [KMP](https://kotlinlang.org/docs/multiplatform.html)) with Compose for Android and Desktop, and see how we can share components between them.

# Modular structure

There are multiple strategies to share Compose components between applications. In our example, we'll create:

- `theme` module to define material theme, typographies, colors, shapes and icons.
- `components` module to define all small reusable components and take `theme` module as dependency.
- `android` module to create mobile screens from `components` module and start an android app.
- `desktop` module to create desktop screens from `components` module and start a jvm app.

We won't share complete screens inside `components` module. That guarantees us a similar user interface and experience for each platform depending their best practices.

# Compose dependencies

Before starting to code, you need to know there are two kinds of dependencies: Compose by Google and Compose by Jetbrains. For both dependencies, artifact identifiers are the same and internal components are declared under `androidx.compose` package.

But there is one main difference between these dependencies, Compose by Google is only compatible for Android apps whereas Compose by Jetbrains can be executed for Android and Desktop apps.

If you want to share components, you must use Compose by Jetbrains and use KMP to write specific code for each platform if necessary.

# Creating multiplatform module

Inside a multiplatform module, you write your common source code shared between platforms but, sometimes, you may want to write specific implementation.

e.g. for android apps, you need Android context when you get a translation from `strings.xml` files. To keep the native feature but to be compatible with others platforms, you need a mechanism to write specific code.

In our case, we write an application compatible with Android and Desktop. You'll find this file tree in multiplatform modules:

```
module
  +- src
    +- commonMain
    +- androidMain
    +- desktopMain
  build.gradle.kts
```

This structure is possible due to the multiplatform gradle plugin where you can specify source set by platform.

**build.gradle.kts**
```kotlin
plugins {
  id("com.android.library")
  kotlin("multiplatform")
  id("org.jetbrains.compose")
}

android {
  // ...
}

kotlin {
  android()
  jvm("desktop")

  sourceSets {
    named("commonMain") {
      dependencies {
        // ...
      }
    }
    named("androidMain") {
      dependencies {
        // ...
      }
    }
    named("desktopMain") {
      dependencies {
        // ...
      }
    }
  }
}
```

It is pretty simple to understand, we apply three plugins to define Android configuration if the module is executed for an Android app, multiplatform plugin to write common and specific source code and Compose desktop to use Jetpack Compose for Android and Desktop.

> **Note:** you can only specify kotlin multiplatform maven dependencies in `commonMain` source set.

# Writing custom implementation

Kotlin define a super simple and powerful way to write specific code for each platform. You just need to create an 
`expect` interface and give `actual` implementations.

e.g. We want to load an image from an URL with Coil but this library is only compatible with Android because it requires Android Context. So, we need to define an expect composable function in our `commonMain` source set and give an actual implementation in Android and Desktop source sets.

**commonMain/src/RemoteImage.kt**
```kotlin
@Composable
expect fun RemoteImage(
  url: String,
  contentDescription: String?,
  modifier: Modifier = Modifier,
  contentScale: ContentScale = ContentScale.Fit
)
```

Android implementation is pretty simple because [Accompanist](https://google.github.io/accompanist/coil/) library provide a Composable compatible with Coil. We just need to define the library in our `androidMain` source set and call it in our actual composable function.

**build.gradle.kts**
```kotlin
kotlin {
  sourceSets {
    named("androidMain") {
      dependencies {
        implementation(Dependencies.Accompanist.coil)
      }
    }
  }
}
```

**androidMain/src/RemoteImage.kt**
```kotlin
@Composable
actual fun RemoteImage(
  url: String,
  contentDescription: String?,
  modifier: Modifier,
  contentScale: ContentScale
) {
  CoilImage(
    data = url,
    modifier = modifier,
    contentDescription = contentDescription,
    contentScale = ContentScale.Crop,
  )
}
```

Desktop implementation is a bit more complex because there is no equivalent to Coil in Desktop environment but Desktop is a jvm ecosystem, you can create your own implementation with [OkHttp client](https://github.com/square/okhttp). Just need more work on the composable. You can find a short implementation below but if you are interested in the whole implementation, check out my GitHub project used to illustrate this blog post.

**build.gradle.kts**
```kotlin
kotlin {
  sourceSets {
    named("desktopMain") {
      dependencies {
        implementation(Dependencies.okhttp)
      }
    }
  }
}
```

**desktopMain/src/RemoteImage.kt**
```kotlin
@Composable
actual fun RemoteImage(
  url: String,
  contentDescription: String?,
  modifier: Modifier,
  contentScale: ContentScale
) {
  val image = remember(url) { mutableStateOf<ImageBitmap?>(null) }
  LaunchedEffect(url) {
    ImageLoader.load(url)?.let {
      image.value = makeFromEncoded(it).asImageBitmap()
    }
  }
  if (image.value != null) {
    Image(
      bitmap = image.value!!,
      contentDescription = contentDescription,
      modifier = modifier,
      contentScale = contentScale
    )
  }
}
```

HTTP request is started only one time when we create our composable, custom helper class `ImageLoader` is used to make the HTTP request and get the image binary and finally, the response is converted to an `ImageBitmap` and used by a standard Composable function from Compose.

From now on, `RemoteImage` can be used everywhere in our project and use the right implementation depending the platform execution from a single composable contract!

# Conclusion

Kotlin Multiplatform is still in alpha but there are interesting perspectives and maybe, it could be a serious competitor for Flutter or React Native. If Compose become compatible with more platforms, like iOS apps, we can create full multiplatform apps with no compromise about performance!

If you want to see a real kotlin multiplatform project, you can check my open source GitHub project about [movies](https://github.com/GerardPaligot/discovering-movies) and if you want to know more about me, you can follow me on Twitter [@GerardPaligot](https://twitter.com/GerardPaligot).

Don't hesitate to chat with me! :)