Kotlin Multiplatform Libraries
Introduction
Kotlin Multiplatform (KMP) allows developers to share code across platforms while still taking advantage of platform-specific features. One of the most powerful aspects of this approach is the ecosystem of libraries designed specifically for multiplatform use. These libraries help address common cross-platform challenges and let you focus on your application's unique features rather than reimplementing basic functionality for each platform.
In this guide, we'll explore essential Kotlin Multiplatform libraries that can help you build robust cross-platform applications. We'll look at libraries for networking, serialization, database management, UI frameworks, and more.
Core Multiplatform Libraries
Kotlin Standard Library
The Kotlin standard library is available on all platforms and provides essential functionality like collections, strings operations, and other utilities.
// Works on all platforms: JVM, Native, and JS
val list = listOf(1, 2, 3, 4, 5)
val doubled = list.map { it * 2 }
val sum = doubled.sum()
println("Sum of doubled values: $sum") // Output: Sum of doubled values: 30
kotlinx.coroutines
Kotlin's coroutines library provides a way to write asynchronous, non-blocking code across platforms.
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
delay(1000L)
println("World!")
}
print("Hello, ")
// Output:
// Hello,
// World!
}
To add kotlinx.coroutines to your project, include this in your build.gradle.kts
file:
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}
}
}
}
kotlinx.serialization
Kotlinx.serialization is a multiplatform serialization library that works across all Kotlin platforms without requiring platform-specific serialization libraries like Gson or Jackson.
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class Person(val name: String, val age: Int)
fun main() {
val json = Json { prettyPrint = true }
// Serialization (object to JSON string)
val person = Person("John Doe", 30)
val jsonString = json.encodeToString(Person.serializer(), person)
println(jsonString)
// Output:
// {
// "name": "John Doe",
// "age": 30
// }
// Deserialization (JSON string to object)
val decodedPerson = json.decodeFromString(Person.serializer(), jsonString)
println(decodedPerson) // Output: Person(name=John Doe, age=30)
}
Add serialization to your project:
plugins {
kotlin("multiplatform")
kotlin("plugin.serialization") version "1.9.0"
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
}
Networking Libraries
Ktor Client
Ktor Client is a multiplatform HTTP client that allows you to make network requests from any Kotlin platform.
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.*
suspend fun fetchData() {
val client = HttpClient()
val response: HttpResponse = client.get("https://api.example.com/data")
println(response.bodyAsText())
client.close()
}
fun main() = runBlocking {
fetchData()
// Output: (JSON response from the API)
}
Add Ktor client to your project:
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.ktor:ktor-client-core:2.3.3")
}
}
val jvmMain by getting {
dependencies {
implementation("io.ktor:ktor-client-okhttp:2.3.3")
}
}
val iosMain by getting {
dependencies {
implementation("io.ktor:ktor-client-darwin:2.3.3")
}
}
}
}
KtorFit
KtorFit is inspired by Retrofit but designed for Kotlin Multiplatform. It simplifies API calls by using interface declarations:
import de.jensklingenberg.ktorfit.http.GET
import de.jensklingenberg.ktorfit.http.Path
interface GitHubApi {
@GET("users/{user}/repos")
suspend fun listRepos(@Path("user") user: String): List<Repository>
}
@Serializable
data class Repository(val name: String, val description: String?)
// Using KtorFit
val ktorfit = Ktorfit.Builder()
.baseUrl("https://api.github.com/")
.build()
val api = ktorfit.create<GitHubApi>()
val repositories = api.listRepos("kotlin")
Database Libraries
SQLDelight
SQLDelight generates type-safe Kotlin APIs from SQL statements, allowing you to work with SQL databases across platforms.
// Define your database schema in .sq file
CREATE TABLE Person (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER NOT NULL
);
-- Queries
insertPerson:
INSERT INTO Person(name, age) VALUES (?, ?);
selectAllPeople:
SELECT * FROM Person;
findPersonById:
SELECT * FROM Person WHERE id = ?;
Using the generated code:
import com.example.db.Database
import com.squareup.sqldelight.db.SqlDriver
import com.squareup.sqldelight.drivers.native.NativeSqliteDriver // for iOS
import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver // for JVM
fun createDatabase(driver: SqlDriver): Database {
Database.Schema.create(driver)
return Database(driver)
}
fun main() {
// Platform-specific driver creation
val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
val database = createDatabase(driver)
// Insert data
database.personQueries.insertPerson("Alice", 30)
database.personQueries.insertPerson("Bob", 25)
// Query data
val allPeople = database.personQueries.selectAllPeople().executeAsList()
allPeople.forEach {
println("Person: ${it.name}, ${it.age}")
}
// Output:
// Person: Alice, 30
// Person: Bob, 25
// Query by id
val person = database.personQueries.findPersonById(1).executeAsOne()
println("Found: ${person.name}, ${person.age}")
// Output: Found: Alice, 30
}
Realm Kotlin
Realm is a mobile database that runs directly inside your application. The Kotlin SDK supports Kotlin Multiplatform:
import io.realm.kotlin.Realm
import io.realm.kotlin.RealmConfiguration
import io.realm.kotlin.ext.query
import io.realm.kotlin.types.RealmObject
import io.realm.kotlin.types.annotations.PrimaryKey
class Person : RealmObject {
@PrimaryKey
var id: String = ""
var name: String = ""
var age: Int = 0
}
// Create Realm instance
val config = RealmConfiguration.Builder(schema = setOf(Person::class))
.name("myRealm.realm")
.build()
val realm = Realm.open(config)
// Insert data
realm.writeBlocking {
copyToRealm(Person().apply {
id = "1"
name = "Alice"
age = 30
})
}
// Query data
val people = realm.query<Person>().find()
people.forEach {
println("Person: ${it.name}, ${it.age}")
}
State Management
MultiPlatform-Settings
This library provides a key-value storage solution that works across platforms:
import com.russhwolf.settings.Settings
import com.russhwolf.settings.get
// Platform-specific settings factory provided elsewhere
val settings: Settings = getSettings()
// Store values
settings.putString("username", "user123")
settings.putInt("loginCount", 5)
settings.putBoolean("isFirstLogin", false)
// Retrieve values
val username = settings.getString("username", "")
val loginCount = settings.getInt("loginCount", 0)
val isFirstLogin = settings.getBoolean("isFirstLogin", true)
println("User: $username has logged in $loginCount times")
// Output: User: user123 has logged in 5 times
UI Libraries
Compose Multiplatform
Compose Multiplatform extends Jetpack Compose to support multiple platforms including desktop, web, and iOS (experimental):
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column {
Text("Count: $count")
Button(onClick = { count++ }) {
Text("Increment")
}
}
}
// Platform-specific entry point
fun main() {
application {
Window(title = "Counter App") {
Counter()
}
}
}
Real-world Example: Building a Weather App
Let's bring several libraries together to create a simple weather application that works across platforms:
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.*
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import com.russhwolf.settings.*
// Data model
@Serializable
data class WeatherData(
val location: String,
val temperature: Double,
val condition: String
)
// API service
class WeatherService(private val apiKey: String) {
private val client = HttpClient()
private val baseUrl = "https://api.weatherservice.com/v1"
suspend fun getWeather(city: String): WeatherData {
val response = client.get("$baseUrl/current?city=$city&apikey=$apiKey")
val json = Json { ignoreUnknownKeys = true }
return json.decodeFromString(response.bodyAsText())
}
}
// Repository with caching
class WeatherRepository(
private val weatherService: WeatherService,
private val settings: Settings
) {
suspend fun getWeatherForCity(city: String): WeatherData {
// Check if we have cached data that's recent
val cachedData = settings.getString("weather_$city", "")
val cacheTime = settings.getLong("weather_${city}_time", 0)
// If cache is less than 30 minutes old, use it
if (cachedData.isNotEmpty() && (System.currentTimeMillis() - cacheTime < 30 * 60 * 1000)) {
return Json.decodeFromString(cachedData)
}
// Otherwise fetch fresh data
return try {
val freshData = weatherService.getWeather(city)
// Update cache
settings.putString("weather_$city", Json.encodeToString(freshData))
settings.putLong("weather_${city}_time", System.currentTimeMillis())
freshData
} catch (e: Exception) {
// If network fails but we have old cache, use it as fallback
if (cachedData.isNotEmpty()) {
return Json.decodeFromString(cachedData)
}
throw e
}
}
}
// ViewModel-like class
class WeatherViewModel(private val repository: WeatherRepository) {
private val _weatherState = MutableStateFlow<WeatherData?>(null)
val weatherState: StateFlow<WeatherData?> = _weatherState
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading
fun loadWeather(city: String) {
viewModelScope.launch {
_isLoading.value = true
try {
_weatherState.value = repository.getWeatherForCity(city)
} catch (e: Exception) {
// Handle error
println("Error: ${e.message}")
} finally {
_isLoading.value = false
}
}
}
}
This example demonstrates how to combine several multiplatform libraries to create a complete application:
- Ktor for API calls
- kotlinx.serialization for parsing JSON
- MultiPlatform-Settings for caching
- Coroutines for asynchronous operations
Summary
Kotlin Multiplatform libraries provide powerful tools for building cross-platform applications without sacrificing native performance or user experience. In this guide, we've covered:
- Core libraries like kotlinx.coroutines and kotlinx.serialization
- Networking libraries like Ktor Client for API communication
- Database solutions including SQLDelight and Realm
- State management with MultiPlatform-Settings
- UI frameworks like Compose Multiplatform
By leveraging these libraries, you can share a significant portion of your codebase across platforms while still maintaining the flexibility to use platform-specific features when needed.
Additional Resources
- Kotlin Multiplatform Official Documentation
- KMP Awesome List - A community-curated list of KMP libraries
- Kotlin Multiplatform Mobile Samples
- SQLDelight Documentation
- Ktor Client Documentation
Exercises
- Basic Exercise: Create a simple notes application that stores data locally using MultiPlatform-Settings.
- Intermediate Exercise: Build a Reddit client that fetches and displays posts from a subreddit using Ktor Client and kotlinx.serialization.
- Advanced Exercise: Develop a weather application similar to our example, but extend it to use SQLDelight for local caching instead of MultiPlatform-Settings, and add Compose Multiplatform UI.
Remember that the multiplatform ecosystem is constantly evolving, so always check for the latest versions and new libraries when starting a new project!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)