A library that allows you to share ViewModels between Android and iOS.
The latest version of the library uses Kotlin version 1.9.0
.
Compatibility versions for older and/or preview Kotlin versions are also available:
Version | Version suffix | Kotlin | Coroutines | AndroidX Lifecycle |
---|---|---|---|---|
latest | no suffix | 1.9.0 | 1.7.2 | 2.6.1 |
1.0.0-ALPHA-10 | no suffix | 1.8.22 | 1.7.2 | 2.6.1 |
1.0.0-ALPHA-9 | no suffix | 1.8.21 | 1.7.1 | 2.5.1 |
1.0.0-ALPHA-8 | no suffix | 1.8.21 | 1.7.0 | 2.5.1 |
1.0.0-ALPHA-7 | no suffix | 1.8.21 | 1.6.4 | 2.5.1 |
1.0.0-ALPHA-6 | no suffix | 1.8.20 | 1.6.4 | 2.5.1 |
1.0.0-ALPHA-4 | no suffix | 1.8.10 | 1.6.4 | 2.5.1 |
1.0.0-ALPHA-3 | no suffix | 1.8.0 | 1.6.4 | 2.5.1 |
Add the library to your shared Kotlin module:
dependencies {
api("com.rickclephas.kmm:kmm-viewmodel-core:1.0.0-ALPHA-11")
}
And create your ViewModels:
import com.rickclephas.kmm.viewmodel.KMMViewModel
import com.rickclephas.kmm.viewmodel.MutableStateFlow
import com.rickclephas.kmm.viewmodel.stateIn
open class TimeTravelViewModel: KMMViewModel() {
private val clockTime = Clock.time
/**
* A [StateFlow] that emits the actual time.
*/
val actualTime = clockTime.map { formatTime(it) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "N/A")
private val _travelEffect = MutableStateFlow<TravelEffect?>(viewModelScope, null)
/**
* A [StateFlow] that emits the applied [TravelEffect].
*/
val travelEffect = _travelEffect.asStateFlow()
}
As you might notice it isn't much different from an Android ViewModel.
The most obvious difference is the KMMViewModel
superclass:
- import androidx.lifecycle.ViewModel
+ import com.rickclephas.kmm.viewmodel.KMMViewModel
- open class TimeTravelViewModel: ViewModel() {
+ open class TimeTravelViewModel: KMMViewModel() {
Besides that there are only 2 minor differences.
The first being a different import for stateIn
:
- import kotlinx.coroutines.flow.stateIn
+ import com.rickclephas.kmm.viewmodel.stateIn
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "N/A")
And the second being a different MutableStateFlow
constructor:
- import kotlinx.coroutines.flow.MutableStateFlow
+ import com.rickclephas.kmm.viewmodel.MutableStateFlow
- private val _travelEffect = MutableStateFlow<TravelEffect?>(null)
+ private val _travelEffect = MutableStateFlow<TravelEffect?>(viewModelScope, null)
These minor differences will make sure that any state changes are propagated to iOS.
Note:
viewModelScope
is a wrapper around the actualCoroutineScope
which can be accessed via theViewModelScope.coroutineScope
property.
I highly recommend you to use the @NativeCoroutinesState
annotation from
KMP-NativeCoroutines
to turn your StateFlow
s into properties in Swift:
@NativeCoroutinesState
val travelEffect = _travelEffect.asStateFlow()
Checkout the KMP-NativeCoroutines README for more information and installation instructions.
Alternative
Alternatively you can create extension properties in your iOS source-set yourself:
val TimeTravelViewModel.travelEffectValue: TravelEffect?
get() = travelEffect.value
Use the view model like you would any other Android view model:
class TimeTravelFragment: Fragment(R.layout.fragment_time_travel) {
private val viewModel: TimeTravelViewModel by viewModels()
}
Note: improved support for Jetpack Compose is coming soon.
Add the Swift package to your Package.swift
file:
dependencies: [
.package(url: "https://github.com/rickclephas/KMM-ViewModel.git", from: "1.0.0-ALPHA-11")
]
Or add it in Xcode by going to File
> Add Packages...
and providing the URL:
https://github.com/rickclephas/KMM-ViewModel.git
.
CocoaPods
If you like you can also use CocoaPods instead of SPM:
pod 'KMMViewModelSwiftUI', '1.0.0-ALPHA-11'
Create a KMMViewModel.swift
file with the following contents:
import KMMViewModelCore
import shared // This should be your shared KMM module
extension Kmm_viewmodel_coreKMMViewModel: KMMViewModel { }
After that you can use your view model almost as if it were an ObservableObject
.
Just use the view model specific property wrappers and functions:
ObservableObject |
KMMViewModel |
---|---|
@StateObject |
@StateViewModel |
@ObservedObject |
@ObservedViewModel |
@EnvironmentObject |
@EnvironmentViewModel |
environmentObject(_:) |
environmentViewModel(_:) |
E.g. to use the TimeTravelViewModel
as a StateObject
:
import SwiftUI
import KMMViewModelSwiftUI
import shared // This should be your shared KMM module
struct ContentView: View {
@StateViewModel var viewModel = TimeTravelViewModel()
}
It's also possible to subclass your view model in Swift:
import Combine
import shared // This should be your shared KMM module
class TimeTravelViewModel: shared.TimeTravelViewModel {
@Published var isResetDisabled: Bool = false
}
You'll need some additional logic if your KMMViewModel
s expose child view models.
First make sure to use the NativeCoroutinesRefinedState
annotation instead of the NativeCoroutinesState
annotation:
class MyParentViewModel: KMMViewModel() {
@NativeCoroutinesRefinedState
val myChildViewModel: StateFlow<MyChildViewModel?> = MutableStateFlow(null)
}
After that you should create a Swift extension property using the childViewModel(_, at:)
function:
extension MyParentViewModel {
var myChildViewModel: MyChildViewModel? {
childViewModel(__myChildViewModel, at: \.__myChildViewModel)
}
}
This will prevent your Swift view models from being deallocated too soon.
Note: for lists, sets and dictionaries containing view models there is
childViewModels(_, at:)
.