Jetpack Libraries Now Support Kotlin Multiplatform: Room, DataStore & ViewModel Guide (2025)
Google's Jetpack libraries now officially support Kotlin Multiplatform. Learn how to use Room 2.8.3, DataStore 1.1.7, and ViewModel 2.9.4 in your shared code with step-by-step guides and production-ready examples.
Posted by
Related reading
Compose Multiplatform for iOS Stable in 2025: What Developers Need to Know
Compose Multiplatform for iOS is now stable with version 1.8.0. Learn how Kotlin Multiplatform's UI framework enables shared UI code across Android and iOS with native performance and 96% code reuse.
KMPShip: Kotlin Multiplatform Starter Kit for Mobile Apps
Discover how KMPShip helps you launch Android and iOS apps faster with a production-ready Kotlin Multiplatform starter built for developers.

The announcement that changes everything for Android developers
Why this matters
- Your Android skills now work on iOS: No need to learn Swift or completely different architectures
- Share the complex parts: Database logic, networking, business rules, and ViewModels
- Keep native UIs: Use Jetpack Compose on Android, SwiftUI on iOS (or share UI with Compose Multiplatform)
- Google's commitment: Official support means long-term maintenance, documentation, and ecosystem growth
What Jetpack libraries support Kotlin Multiplatform?
Stable & Production-Ready
- Platforms: Android, iOS, JVM (Desktop)
- Full suspend function support and Flow-based reactive queries
- Official Documentation
- Preferences DataStore supported in KMP
- Type-safe key-value storage with coroutines
- Official Documentation
- Full lifecycle-aware components in shared code
- ViewModelFactory pattern supported
- Testing utilities included
- Official Documentation
- Collections
- SavedState
- Paging
- Navigation Compose (2.9.1)
Room 2.8.3: Your database, everywhere
What's new in Room 2.8.3 for KMP
- KSP2 support: Faster compilation and better error messages
- BundledSQLiteDriver: Consistent SQLite behavior across all platforms
- Coroutine-native: Suspend functions and Flow are first-class citizens
Setting up Room for Kotlin Multiplatform
shared/build.gradle.kts file:kotlinkotlin { sourceSets { commonMain.dependencies { implementation("androidx.room:room-runtime:2.8.3") implementation("androidx.sqlite:sqlite-bundled:2.5.0-alpha11") } } } dependencies { add("kspCommonMainMetadata", "androidx.room:room-compiler:2.8.3") }
kotlin// shared/src/commonMain/kotlin/com/example/app/database/AppDatabase.kt @Database(entities = [Task::class], version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun taskDao(): TaskDao } @Entity(tableName = "tasks") data class Task( @PrimaryKey(autoGenerate = true) val id: Long = 0, val title: String, val isCompleted: Boolean = false, val createdAt: Long = System.currentTimeMillis() ) @Dao interface TaskDao { @Query("SELECT * FROM tasks ORDER BY createdAt DESC") fun getAllTasks(): Flow<List<Task>> @Insert suspend fun insertTask(task: Task) @Update suspend fun updateTask(task: Task) @Delete suspend fun deleteTask(task: Task) @Query("SELECT * FROM tasks WHERE isCompleted = :completed") fun getTasksByStatus(completed: Boolean): Flow<List<Task>> }
kotlin// shared/src/commonMain/kotlin/com/example/app/database/DatabaseBuilder.kt expect class DatabaseBuilder { fun build(): AppDatabase } fun getDatabaseBuilder(): DatabaseBuilder = DatabaseBuilder()
kotlin// shared/src/androidMain/kotlin/com/example/app/database/DatabaseBuilder.android.kt import android.content.Context import androidx.room.Room import androidx.sqlite.driver.bundled.BundledSQLiteDriver actual class DatabaseBuilder(private val context: Context) { actual fun build(): AppDatabase { val dbFile = context.getDatabasePath("app_database.db") return Room.databaseBuilder<AppDatabase>( context = context, name = dbFile.absolutePath ) .setDriver(BundledSQLiteDriver()) .build() } }
kotlin// shared/src/iosMain/kotlin/com/example/app/database/DatabaseBuilder.ios.kt import androidx.room.Room import androidx.sqlite.driver.bundled.BundledSQLiteDriver import platform.Foundation.NSDocumentDirectory import platform.Foundation.NSFileManager import platform.Foundation.NSUserDomainMask actual class DatabaseBuilder { actual fun build(): AppDatabase { val documentDirectory = NSFileManager.defaultManager.URLForDirectory( directory = NSDocumentDirectory, inDomain = NSUserDomainMask, appropriateForURL = null, create = false, error = null ) val dbFile = requireNotNull(documentDirectory?.path) + "/app_database.db" return Room.databaseBuilder<AppDatabase>( name = dbFile ) .setDriver(BundledSQLiteDriver()) .build() } }
Migration from Android-only Room
- Move entity and DAO definitions to
commonMain(they're pure Kotlin, no changes needed) - Update queries to use suspend functions and Flow (if not already)
- Create platform-specific database builders using the expect/actual pattern shown above
- Test on both platforms to ensure schema consistency
DataStore 1.1.7: Type-safe preferences in shared code
Why use DataStore in Kotlin Multiplatform?
- Type safety: Strong typing prevents runtime errors
- Coroutine-native: Async operations without callbacks
- Crash-safe: Atomic write operations with transactions
- Observable: React to changes with Flow
- Consistent API: Same code on all platforms
Setting up DataStore for KMP
kotlinkotlin { sourceSets { commonMain.dependencies { implementation("androidx.datastore:datastore:1.1.7") implementation("androidx.datastore:datastore-preferences:1.1.7") } } }
kotlin// shared/src/commonMain/kotlin/com/example/app/data/DataStoreFactory.kt import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences expect fun createDataStore(): DataStore<Preferences>
kotlin// shared/src/androidMain/kotlin/com/example/app/data/DataStoreFactory.android.kt import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.preferencesDataStore private val Context.dataStore: DataStore<Preferences> by preferencesDataStore( name = "app_preferences" ) actual fun createDataStore(): DataStore<Preferences> { // Context must be passed from Android application return appContext.dataStore } // Helper to set context from Android app private lateinit var appContext: Context fun initDataStore(context: Context) { appContext = context.applicationContext }
kotlin// shared/src/iosMain/kotlin/com/example/app/data/DataStoreFactory.ios.kt import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.PreferenceDataStoreFactory import kotlinx.cinterop.ExperimentalForeignApi import okio.Path.Companion.toPath import platform.Foundation.NSDocumentDirectory import platform.Foundation.NSFileManager import platform.Foundation.NSUserDomainMask @OptIn(ExperimentalForeignApi::class) actual fun createDataStore(): DataStore<Preferences> { val documentDirectory = NSFileManager.defaultManager.URLForDirectory( directory = NSDocumentDirectory, inDomain = NSUserDomainMask, appropriateForURL = null, create = false, error = null ) val path = requireNotNull(documentDirectory?.path) + "/app_preferences.preferences_pb" return PreferenceDataStoreFactory.createWithPath( produceFile = { path.toPath() } ) }
kotlin// shared/src/commonMain/kotlin/com/example/app/data/PreferencesRepository.kt import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map class PreferencesRepository(private val dataStore: DataStore<Preferences>) { private companion object { val THEME_MODE_KEY = stringPreferencesKey("theme_mode") val NOTIFICATIONS_ENABLED_KEY = booleanPreferencesKey("notifications_enabled") } val themeMode: Flow<String?> = dataStore.data.map { preferences -> preferences[THEME_MODE_KEY] } val notificationsEnabled: Flow<Boolean> = dataStore.data.map { preferences -> preferences[NOTIFICATIONS_ENABLED_KEY] ?: true } suspend fun setThemeMode(mode: String) { dataStore.edit { preferences -> preferences[THEME_MODE_KEY] = mode } } suspend fun setNotificationsEnabled(enabled: Boolean) { dataStore.edit { preferences -> preferences[NOTIFICATIONS_ENABLED_KEY] = enabled } } }
ViewModel 2.9.4: Lifecycle-aware logic everywhere
Setting up ViewModel for KMP
kotlinkotlin { sourceSets { commonMain.dependencies { api("androidx.lifecycle:lifecycle-viewmodel:2.9.4") } } }
api instead of implementation for better Swift interoperability.kotlin// shared/src/commonMain/kotlin/com/example/app/presentation/TaskListViewModel.kt import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch class TaskListViewModel( private val taskDao: TaskDao ) : ViewModel() { private val _uiState = MutableStateFlow<TaskListUiState>(TaskListUiState.Loading) val uiState: StateFlow<TaskListUiState> = _uiState.asStateFlow() init { loadTasks() } private fun loadTasks() { viewModelScope.launch { taskDao.getAllTasks().collect { tasks -> _uiState.value = TaskListUiState.Success(tasks) } } } fun addTask(title: String) { viewModelScope.launch { taskDao.insertTask(Task(title = title)) } } fun toggleTask(task: Task) { viewModelScope.launch { taskDao.updateTask(task.copy(isCompleted = !task.isCompleted)) } } fun deleteTask(task: Task) { viewModelScope.launch { taskDao.deleteTask(task) } } } sealed class TaskListUiState { object Loading : TaskListUiState() data class Success(val tasks: List<Task>) : TaskListUiState() data class Error(val message: String) : TaskListUiState() }
kotlin// shared/src/commonMain/kotlin/com/example/app/di/ViewModelFactory.kt import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory val taskListViewModelFactory = viewModelFactory { initializer { TaskListViewModel( taskDao = getDatabaseBuilder().build().taskDao() ) } }
kotlin// shared/src/commonMain/kotlin/com/example/app/ui/TaskListScreen.kt @Composable fun TaskListScreen(viewModel: TaskListViewModel = viewModel(factory = taskListViewModelFactory)) { val uiState by viewModel.uiState.collectAsState() when (val state = uiState) { is TaskListUiState.Loading -> { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { CircularProgressIndicator() } } is TaskListUiState.Success -> { TaskList( tasks = state.tasks, onTaskClick = { viewModel.toggleTask(it) }, onDeleteTask = { viewModel.deleteTask(it) } ) } is TaskListUiState.Error -> { ErrorScreen(message = state.message) } } }
swift// iosApp/TaskListView.swift import SwiftUI import shared struct TaskListView: View { @StateObject private var viewModel = TaskListViewModel( taskDao: DatabaseBuilder().build().taskDao() ) var body: some View { // SwiftUI implementation } }
Real-world migration: Android app to KMP
Before: Android-only architecture
kotlin// Android app/src/main/kotlin/com/example/app/data/TaskRepository.kt class TaskRepository(private val taskDao: TaskDao) { fun getAllTasks(): Flow<List<Task>> = taskDao.getAllTasks() suspend fun addTask(task: Task) = taskDao.insertTask(task) } // Android app/src/main/kotlin/com/example/app/ui/TaskViewModel.kt class TaskViewModel(private val repository: TaskRepository) : ViewModel() { val tasks: StateFlow<List<Task>> = repository.getAllTasks() .stateIn(viewModelScope, SharingStarted.Lazily, emptyList()) }
After: Shared KMP code
shared/src/commonMainkotlin// shared/src/commonMain/kotlin/com/example/app/data/TaskRepository.kt class TaskRepository(private val taskDao: TaskDao) { fun getAllTasks(): Flow<List<Task>> = taskDao.getAllTasks() suspend fun addTask(task: Task) = taskDao.insertTask(task) }
kotlin// shared/src/commonMain/kotlin/com/example/app/presentation/TaskViewModel.kt class TaskViewModel(private val repository: TaskRepository) : ViewModel() { val tasks: StateFlow<List<Task>> = repository.getAllTasks() .stateIn(viewModelScope, SharingStarted.Lazily, emptyList()) }
kotlin// Android app/src/main/kotlin/com/example/app/MainActivity.kt class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Initialize shared module with Android context initDataStore(this) setContent { val viewModel: TaskViewModel = viewModel( factory = taskViewModelFactory ) TaskListScreen(viewModel) } } }
What you've achieved
Getting started with KMPShip: Skip the setup complexity
- Setting up KSP and room compiler correctly
- Configuring platform-specific database and DataStore paths
- Setting up dependency injection
- Configuring CI/CD for both platforms
- Adding authentication, payments, and other production features
KMPShip includes everything you need:
- ✅ Production-ready KMP architecture
- ✅ Dependency injection setup (Koin)
- ✅ Complete sample app with best practices
- ✅ Authentication (Google, Apple, Email)
- ✅ In-app purchases and subscriptions
- ✅ Push notifications
- ✅ CI/CD pipelines
- ✅ Complete sample app
The bottom line for 2025
Sources and references
- Android's Kotlin Multiplatform announcements at Google I/O and KotlinConf 25 (Android Developers Blog)
- Set up Room Database for KMP (Android Developers)
- Room 2.8.3 Release Notes (Android Developers)
- Set up DataStore for KMP (Android Developers)
- Set up ViewModel for KMP (Android Developers)
- Add Kotlin Multiplatform to an existing project (Android Developers)
- What's Next for Kotlin Multiplatform, August 2025 Update (JetBrains Kotlin Blog)
Continue Your KMP Journey
- KMP vs Flutter vs React Native comparison: Choose the right framework
- How to Set Up Kotlin Multiplatform: Complete setup guide
- Compose Multiplatform for iOS Stable: Share UI code too
Start Building with Jetpack Libraries Today
Build your KMP app faster
Skip the setup and start shipping with a production-ready Kotlin Multiplatform starter kit.