Freelancing for Pale Blue

Looking for flexible work opportunities that fit your schedule?


A primer into native interactions in Compose Multiplatform apps

Compose Multiplatform Dec 19, 2024

Kotlin Multiplatform (KMP) offers a powerful way to write shared code across multiple platforms while still giving you the flexibility to define platform-specific implementations. A key part of achieving this is using the expect/actual mechanism, which allows you to define common APIs in your shared module and provide platform-specific implementations in the respective targets.

This approach is particularly handy when building Compose Multiplatform apps, where you might need to perform native system interactions like opening a URL in the browser or sharing text with the native operating system mechanism.

In this blog post, we'll explore how to set up and use the expect/actual pattern to create a SystemService that handles these interactions seamlessly across platforms and uses a Koin injection for dependency injection. By the end, you'll see how this technique keeps your codebase clean and efficient while enabling platform-specific capabilities.

The interface

interface SystemService {
    fun launchUrl(url: String)
    fun shareText(details: String)
}

expect fun Module.factorySystemService(): KoinDefinition<SystemService>

SystemService.kt

A pure interface that defines what interactions we need with the platform.

We use Koin for dependency injection and we define a module that needs to be implemented in each platform (expect).

The implementations

internal class AndroidSystemService(
    private val context: Context
) : SystemService {

    override fun launchUrl(url: String) {
        if (url.isBlank()) return
        val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        }
        intent.resolveActivity(context.packageManager)?.let {
            context.startActivity(intent)
        }
    }

    override fun shareText(details: String) {
        val intent = Intent().apply {
            type = "text/plain"
            action = Intent.ACTION_SEND
            putExtra(Intent.EXTRA_TEXT, details)
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        }
        intent.resolveActivity(context.packageManager)?.let {
            context.startActivity(intent)
        }
    }
}

actual fun Module.factorySystemService(): KoinDefinition<SystemService> {
    return factoryOf(::AndroidSystemService)
}

SystemService.android.kt

internal class IosSystemService : SystemService {

    override fun launchUrl(url: String) {
        if (url.isBlank()) return
        val nsUrl = NSURL(string = url)
        if (UIApplication.sharedApplication.canOpenURL(nsUrl)) {
            UIApplication.sharedApplication.openURL(nsUrl)
        }
    }

    override fun shareText(details: String) {
        val activityController = UIActivityViewController(
            activityItems = listOf(details),
            applicationActivities = null
        )
        val window = UIApplication.sharedApplication.windows().first() as UIWindow?
        activityController.popoverPresentationController()?.sourceView =
            window
        window?.rootViewController?.presentViewController(
            activityController as UIViewController,
            animated = true,
            completion = null
        )
    }
}

actual fun Module.factorySystemService(): KoinDefinition<SystemService> {
    return factoryOf(::IosSystemService)
}

SystemService.ios.kt

We implement the functionality on each of the supported platforms, here Android and iOS, and provide an actual implementation for the Koin module that instantiates the SystemService implementation for each platform.

Usage

Use the factorySystemService() in a Koin module and inject SystemService. You will get the correct implementation for each platform to perform your actions.

Happy coding!

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.