Networking in Kotlin Multiplatform Mobile Using Ktor

Jhaman
5 min readJul 13, 2023

--

Image by arconsis

In today’s mobile app development landscape, data transfer between clients and web servers is a fundamental requirement. Consequently, having a reliable HTTP client is essential for any application.

When working on Kotlin Multiplatform Mobile (KMM) projects, building an HTTP client from scratch is unnecessary. KMM allows developers to utilize platform-specific APIs, giving them the freedom to choose between low-level iOS and Android libraries or robust third-party HTTP clients. Popular choices include OkHttp for Android and Alamofire/UrlSession for iOS. By incorporating these libraries into the platform source sets, creating a shared interface, and writing common logic in the shared module, developers can streamline their codebase without worrying about platform differences.

Introducing Ktor–The All-in-One Solution

Ktor is an Open Source framework designed to simplify the development of connected applications. With a focus on web applications, HTTP services, mobile, and browser applications, Ktor aims to provide an end-to-end, multi-platform application framework. One of its standout features is the multi-platform asynchronous HTTP client, which enables developers to effortlessly send requests and handle responses. Furthermore, Ktor offers additional functionality, including authentication, JSON serialization, and more.

In this article, we will delve into the capabilities of Ktor’s multi-platform asynchronous HTTP client. Our exploration will cover the entire spectrum, from initial setup to making requests and incorporating features that simplify the development process. Say goodbye to complex platform differences and embrace the convenience of Ktor as we embark on building our new KMM application!

How To Use ktor's Advantages

First, create a KMM project by selecting Kotlin Multiplatform App in Android Studio. Don’t forget to switch to the project view!

Creating a sample KMM project

Ready to implement some business logic? Let's start by adding the required dependencies to the common main source set of the KMM module. Ktor provides separate artifacts for using the HTTP client — a common module and different engines that process the network request. It also offers several engines to use in your project, including CIO, Android, iOS, and others. Engines differ in their set of supported features or platforms they work on. You can check the Ktor documentation here.

Add Ktor dependencies in commainMain , androidMain and iOSMain in shared level build.gradle.

Ktor dependencies in Shared build.gradle

All main dependencies setup is done. We are now ready to use the Ktor client. In this article we are injecting the httpClient object by using Koin dependency injection, so you need to have prior knowledge of how to use Koin.

In this tutorial, we will use a dummy API. It's completely free and you can use it for learning purposes.

Step 1: Create a Network API class under the API layer and inject ktorClient by using Koin dependency injection.

Make sure you have added content negotiation and JSON parsing serialization dependencies with the Ktor client. Read more about them here

internal class ProductApi(private val ktorClient: HttpClient) {
suspend fun getProducts(productId: Int): Result<ProductDto> = runCatching {
return@runCatching ktorClient.get("products/$productId").body()
}
}

Step 2: Access the Product API inside the repository class by injecting productApi using Koin DI.

class ProductRepository internal constructor(
private val productApi: ProductApi,
) {

suspend fun getProducts(productId: Int) = flow<RepositoryResult<out Product>> {
productApi.getProducts(productId)
.onSuccess {
emit(RepositoryResult.Success(it.toProduct()))
}.onFailure {
emit(RepositoryResult.Error(null, "TODO)"))
}
}.onStart {
emit(RepositoryResult.Loading)
}
}

Now expose the use case in the shared module to utilize by both clients, i.e Android and iOS.

class ProductUseCase(private val productRepository: ProductRepository) {
suspend operator fun invoke(productId: Int): Flow<RepositoryResult<out Product>> =
productRepository.getProducts(productId)
}

Step 3: Utilize ProductUseCase in the UI layer like below in Android ViewModel same as for iOS:

class MainViewModel(private val productUseCase: ProductUseCase) : ViewModel() {
private val _uiData = MutableStateFlow(MainUIState())
val uiData: StateFlow<MainUIState>
get() = _uiData.asStateFlow()

init {
getProductData()
}

private fun getProductData(productId: Int = 1) {
viewModelScope.launch {
productUseCase(productId).collect { it->
when (it) {
is RepositoryResult.Error -> _uiData.update { prev -> prev.copy(loading = ContentState.ERROR) }
is RepositoryResult.Loading -> _uiData.update { prev -> prev.copy(loading = ContentState.LOADING) }
is RepositoryResult.Success -> _uiData.update { prev ->
prev.copy(
loading = ContentState.SUCCESS, product = it.data
)
}
}
}
}
}
}

Since KMM allows sharing code across different platforms, you need to handle platform-specific code for iOS and Android separately. For example, you may need to handle differences in network permissions, SSL certificates, or other platform-specific configurations.

You can use the expect and actual declarations to handle platform-specific code. Define platform-specific implementations in the expect declaration and provide the actual implementations in the platform-specific modules (e.g., androidMain and iosMain).

Please refer to our previous article for more information on the declaration of “expect” and “actual”.

And that’s it! You can now use Ktor to perform networking operations in your Kotlin Multiplatform Mobile (KMM) project. Remember to handle errors, implement error-handling strategies, and consider other best practices for networking requests in your application.

Conclusion

In conclusion, using Ktor for networking in Kotlin Multiplatform Mobile (KMM) projects allows you to set up a shared codebase for network requests. By configuring a Ktor client and utilizing its features, such as platform support, JSON serialization, and flexible request methods, you can efficiently perform network operations. However, it’s crucial to handle errors, consider platform-specific code, and stay updated with Ktor and KMM versions. Overall, Ktor simplifies networking in KMM and enables seamless data exchange across multiple platforms.

If you liked the article, feel like there is something missing or have further questions, please share your thoughts and feedback in the comments. Your input is valuable and will contribute to the ongoing discussion around Kotlin Multiplatform Mobile (KMM) and its benefits in cross-platform app development.

I look forward to connecting with you again in my upcoming articles!

--

--