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!
Top comments (0)