Upskill/Reskill
Jul 5, 2024

Use Kotlin Multiplatform With a Network Listener Project

Mofe Ejegi
7 minutes

Kotlin Multiplatform (KMP) simplifies implementing shared features in cross-platform projects, allowing you to set up platform-specific dependencies without having to completely overhaul your codebase. Its flexibility and versatility make KMP an exceptional and thoughtfully designed development framework.

In the first article in this series, you put all the foundational elements in place to start using Kotlin Multiplatform. Now you’re ready to implement platform-specific dependencies from their respective modules. A practical example to illustrate this is creating a simple network listener.

To help guide you, I have created a video to showcase the result of your work.

Start Using Kotlin Multiplatform

If you’ve developed apps before, you’ve probably encountered scenarios where you needed to inform users about their internet connectivity, be it an offline status or a disabled connection. Android developers typically employ the ConnectivityManager, while iOS developers might use NWPathMonitor.

Accessing ConnectivityManager in the Android module is straightforward with the androidContext() function provided by the Koin dependency injection (DI) framework for KMP. However, in December 2023, I observed that NWPathMonitor isn’t included in the prebuilt libraries for Kotlin Native, so I used an interface to integrate it using Swift code. This approach involves leveraging the IosApplicationComponent to bridge the NWPathMonitor class from the iosApp module to the composeApp module shared by Android and iOS.

First I’ll conceptualize the interface. At its core, the network listener must perform two fundamental actions: initiating the listening process and ceasing it when necessary. To do so, define two functions: registerListener to start the listening and unregisterListener to stop it; then encapsulate these functionalities within an interface named NetworkHelper. This interface is also designed to notify you about network status changes, providing callbacks to indicate when a network connection is established or lost.

// NetworkHelper.kt (In commonMain)
package org.example.project.platform

interface NetworkHelper {
fun registerListener(onNetworkAvailable: () -> Unit, onNetworkLost: () -> Unit)
fun unregisterListener()
}

Implementing this interface on Android is pretty straightforward. Go to the exact location in your androidMain module and create a new class called AndroidNetworkHelper, which implements the NetworkHelper interface. This is a pretty standard implementation.

// AndroidNetworkHelper.kt
package org.example.project.platform

import android.content.Context
import android.content.Context.CONNECTIVITY_SERVICE
import android.net.ConnectivityManager
import android.net.Network

class AndroidNetworkHelper(context: Context) : NetworkHelper {
private var networkCallback: ConnectivityManager.NetworkCallback? = null
private val connectivityManager = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager

override fun registerListener(onNetworkAvailable: () -> Unit, onNetworkLost: () -> Unit) {
networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
onNetworkAvailable()
}
override fun onUnavailable() {
onNetworkLost()
}
override fun onLost(network: Network) {
onNetworkLost()
}
}
networkCallback?.let { connectivityManager.registerDefaultNetworkCallback(it) }
}

override fun unregisterListener() {
networkCallback?.let { connectivityManager.unregisterNetworkCallback(it) }
}
}

You still want to make this class available in the dependency injection (DI) graph described in the previous article.

// PlatformModule.android.kt
actual val platformModule = module {
single<NetworkHelper> { AndroidNetworkHelper(androidContext()) }
}

Now for the fun part: In the iosApp module, go into the iosApp folder and create a new Swift class file called IosNetworkHelper. You should do this using JetBrains Fleet or Apple’s XCode code editor:

// IosNetworkHelper.swift
import Foundation
import Network
import composeApp

class IosNetworkHelper : NetworkHelper {
private let monitor: NWPathMonitor = NWPathMonitor()

func registerListener(onNetworkAvailable: @escaping () -> Void, onNetworkLost: @escaping () -> Void) {
monitor.pathUpdateHandler = { path in
if path.status == .satisfied {
onNetworkAvailable()
} else {
onNetworkLost()
}
}
monitor.start(queue: DispatchQueue.global(qos: .background))
}

func unregisterListener() {
monitor.cancel()
}
}

Notice that the Unit return type is now represented as a Void type. That’s due to the Kotlin Native Obj-C interoperability mappings.

Now that you have an iOS implementation leveraging the Network package native to the iOS ecosystem, you can integrate it into the IosApplicationComponent. To do this effectively, first define it within the component:

// IosApplicationComponent.kt (In iosMain)
package org.example.project.platform
class IosApplicationComponent(val networkHelper: NetworkHelper)
// We've removed the old 'application()' function
// iOSApp.swift
import SwiftUI
import composeApp
@main
struct iOSApp: App {
init() {
KoinInit_iosKt.doInitKoinIos(
appComponent: IosApplicationComponent(
networkHelper: IosNetworkHelper() // Note this
)
)
}
var body: some Scene {...}
}

Lastly, to piece the puzzle together, make it available to Koin:

// PlatformModule.ios.kt
actual val platformModule = module {
single<NetworkHelper> { get<IosApplicationComponent>().networkHelper }
}

Complete the Implementations

You now have everything you need to complete your NetworkListenerimplementation. Return to the commonMain source set and create a class called NetworkListener:

// NetworkListener.kt
class NetworkListener(private val helper: NetworkHelper) {
val networkStatus: Flow<NetworkStatus> = callbackFlow {
helper.registerListener(
onNetworkAvailable = {
trySend(NetworkStatus.Connected)
},
onNetworkLost = {
trySend(NetworkStatus.Disconnected)
}
)

awaitClose {
helper.unregisterListener()
}
}.distinctUntilChanged().flowOn(Dispatchers.IO)
}
sealed class NetworkStatus {
data object Connected : NetworkStatus()
data object Disconnected : NetworkStatus()
}

This class utilizes Kotlin’s flows to tap into the capabilities of the NetworkHelper methods you established previously. This NetworkListenerclass can be seamlessly injected into the DI graph within the commonMainsource set, further streamlining its integration and use.

// CommonModule.kt (In commonMain source set)
package org.example.project.di

import org.example.project.platform.NetworkListener
import org.koin.dsl.module

val commonModule = module {
single { NetworkListener(get()) }
}

To wrap it all up, go to the App.kt composable and test the network connectivity:

// App.kt
@Composable
fun App(
networkListener: NetworkListener = koinInject(),
) {
KoinContext {
MaterialTheme {
val networkStatus by networkListener.networkStatus.collectAsState(NetworkStatus.Connected)
Text(text = networkStatus.toString())
}
}
}

Conclusion

This two-part article provided a step-by-step guide to calling platform-specific dependencies in Kotlin Multiplatform. While I’ve applied some creative approaches, the majority of the patterns discussed are derived from various sources, including blogs, official Kotlin and JetBrains articles, documentation and KMP samples, including:

The shift toward remote and borderless hiring enables companies to look beyond geographical constraints to access global talent. Explore strategies to build high-performing, globally distributed teams by downloading The Future of Hiring Is Borderless.

About the author

Mofe Ejegi

Mofe Ejegi is a software engineer with a primary focus on mobile development. He is based in Delta state, Nigeria, and is a member of the Andela Talent Network, a private global marketplace for digital talent. He currently works as a Senior Android engineer at Banque Misr where he demonstrates his strong background in FinTech. On the side, he actively participates in the official Kotlin community on Slack, mostly researching the latest developments in the Kotlin and Android ecosystem, particularly on Kotlin Multiplatform.

Interested in 
Learning More?

Subscribe today to stay informed and get regular updates from Andela.

You might also be interested in

Ready to get started?

Contact Us