{"id":15055337,"url":"https://github.com/rickclephas/kmp-observableviewmodel","last_synced_at":"2026-04-08T14:32:33.083Z","repository":{"id":63928749,"uuid":"568920097","full_name":"rickclephas/KMP-ObservableViewModel","owner":"rickclephas","description":"Library to use AndroidX/Kotlin ViewModels with SwiftUI","archived":false,"fork":false,"pushed_at":"2025-05-07T19:30:42.000Z","size":637,"stargazers_count":630,"open_issues_count":11,"forks_count":28,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-05-15T12:05:05.305Z","etag":null,"topics":["combine","ios","kmm","kmp","kotlin","kotlin-multiplatform","kotlin-multiplatform-mobile","swift","swiftui","viewmodel"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rickclephas.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-11-21T17:35:48.000Z","updated_at":"2025-05-15T00:59:20.000Z","dependencies_parsed_at":"2023-02-13T03:01:19.176Z","dependency_job_id":"c30c7c58-fdb4-42f6-b393-e71ef93762c6","html_url":"https://github.com/rickclephas/KMP-ObservableViewModel","commit_stats":{"total_commits":307,"total_committers":3,"mean_commits":"102.33333333333333","dds":0.006514657980456029,"last_synced_commit":"805c41ff1305b02909c3b50a7e34a46b3c029c04"},"previous_names":["rickclephas/kmp-observableviewmodel","rickclephas/kmm-viewmodel"],"tags_count":70,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rickclephas%2FKMP-ObservableViewModel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rickclephas%2FKMP-ObservableViewModel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rickclephas%2FKMP-ObservableViewModel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rickclephas%2FKMP-ObservableViewModel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rickclephas","download_url":"https://codeload.github.com/rickclephas/KMP-ObservableViewModel/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254337612,"owners_count":22054253,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["combine","ios","kmm","kmp","kotlin","kotlin-multiplatform","kotlin-multiplatform-mobile","swift","swiftui","viewmodel"],"created_at":"2024-09-24T21:40:58.842Z","updated_at":"2026-02-09T23:06:53.374Z","avatar_url":"https://github.com/rickclephas.png","language":"Kotlin","readme":"# KMP-ObservableViewModel\n\nA library (previously known as KMM-ViewModel) that allows you to use AndroidX/Kotlin ViewModels with SwiftUI.\n\n## Compatibility\n\nYou can use this library in any KMP project,\nbut not all targets support AndroidX and/or SwiftUI interop:\n\n| Target     |  Supported  | AndroidX | SwiftUI |\n|------------|:-----------:|:--------:|:-------:|\n| Android    |      ✅      |    ✅     |    -    |\n| JVM        |      ✅      |    ✅     |    -    |\n| iOS        |      ✅      |    ✅     |    ✅    |\n| macOS      |      ✅      |    ✅     |    ✅    |\n| tvOS       |      ✅      |    -     |    ✅    |\n| watchOS    |      ✅      |    -     |    ✅    |\n| linuxX64   |      ✅      |    ✅     |    -    |\n| linuxArm64 |      ✅      |    -     |    -    |\n| mingwX64   |      ✅      |    -     |    -    |\n| JS         |      ✅      |    -     |    -    |\n| Wasm       |      ✅      |    -     |    -    |\n\nThe latest version of the library uses Kotlin version `2.3.10`.  \nCompatibility versions for older and/or preview Kotlin versions are also available:\n\n| Version       | Version suffix  |   Kotlin   | Coroutines | AndroidX Lifecycle |\n|---------------|-----------------|:----------:|:----------:|:------------------:|\n| **_latest_**  | **_no suffix_** | **2.3.10** | **1.10.1** |     **2.8.7**      |\n| 1.0.1         | _no suffix_     |   2.3.0    |   1.10.1   |       2.8.7        |\n| 1.0.0         | _no suffix_     |   2.2.21   |   1.10.1   |       2.8.7        |\n| 1.0.0-BETA-13 | _no suffix_     |   2.2.10   |   1.10.1   |       2.8.7        |\n| 1.0.0-BETA-12 | _no suffix_     |   2.2.0    |   1.10.1   |       2.8.7        |\n| 1.0.0-BETA-11 | _no suffix_     |   2.1.21   |   1.10.1   |       2.8.7        |\n| 1.0.0-BETA-9  | _no suffix_     |   2.1.10   |   1.10.1   |       2.8.7        |\n| 1.0.0-BETA-8  | _no suffix_     |   2.1.0    |   1.9.0    |       2.8.4        |\n| 1.0.0-BETA-7  | _no suffix_     |   2.0.21   |   1.9.0    |       2.8.4        |\n| 1.0.0-BETA-4  | _no suffix_     |   2.0.10   |   1.8.1    |       2.8.4        |\n| 1.0.0-BETA-3  | _no suffix_     |   2.0.0    |   1.8.1    |       2.8.0        |\n\n## Kotlin\n\nAdd the library to your shared Kotlin module and opt-in to the `ExperimentalForeignApi`:\n```kotlin\nkotlin {\n    sourceSets {\n        all {\n            languageSettings.optIn(\"kotlinx.cinterop.ExperimentalForeignApi\")\n        }\n        commonMain {\n            dependencies {\n                api(\"com.rickclephas.kmp:kmp-observableviewmodel-core:1.0.2\")\n            }\n        }\n    }\n}\n```\n\nAnd create your ViewModels:\n```kotlin\nimport com.rickclephas.kmp.observableviewmodel.ViewModel\nimport com.rickclephas.kmp.observableviewmodel.MutableStateFlow\nimport com.rickclephas.kmp.observableviewmodel.stateIn\n\nopen class TimeTravelViewModel: ViewModel() {\n\n    private val clockTime = Clock.time\n\n    /**\n     * A [StateFlow] that emits the actual time.\n     */\n    val actualTime = clockTime.map { formatTime(it) }\n        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), \"N/A\")\n\n    private val _travelEffect = MutableStateFlow\u003cTravelEffect?\u003e(viewModelScope, null)\n    /**\n     * A [StateFlow] that emits the applied [TravelEffect].\n     */\n    val travelEffect = _travelEffect.asStateFlow()\n}\n```\n\nAs you might notice it isn't much different from an AndroidX ViewModel.  \nWe are obviously using a different `ViewModel` superclass:\n\n```diff\n- import androidx.lifecycle.ViewModel\n+ import com.rickclephas.kmp.observableviewmodel.ViewModel\n\nopen class TimeTravelViewModel: ViewModel() {\n```\n\nBut besides that there are only 2 minor differences.  \nThe first being a different import for `stateIn`:\n\n```diff\n- import kotlinx.coroutines.flow.stateIn\n+ import com.rickclephas.kmp.observableviewmodel.stateIn\n\n        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), \"N/A\")\n```\n\nAnd the second being a different `MutableStateFlow` constructor:\n\n```diff\n- import kotlinx.coroutines.flow.MutableStateFlow\n+ import com.rickclephas.kmp.observableviewmodel.MutableStateFlow\n\n-    private val _travelEffect = MutableStateFlow\u003cTravelEffect?\u003e(null)\n+    private val _travelEffect = MutableStateFlow\u003cTravelEffect?\u003e(viewModelScope, null)\n```\n\nThese minor differences will make sure that state changes are propagated to SwiftUI.  \n\n\u003e [!NOTE]\n\u003e `viewModelScope` is a wrapper around the actual `CoroutineScope` which can be accessed \n\u003e via the `ViewModelScope.coroutineScope` property.\n\n### KMP-NativeCoroutines\n\nI highly recommend you to use the `@NativeCoroutinesState` annotation from\n[KMP-NativeCoroutines](https://github.com/rickclephas/KMP-NativeCoroutines)\nto turn your `StateFlow`s into properties in Swift:\n\n```kotlin\n@NativeCoroutinesState\nval travelEffect = _travelEffect.asStateFlow()\n```\n\nCheckout the KMP-NativeCoroutines [README](https://github.com/rickclephas/KMP-NativeCoroutines/blob/master/README.md)\nfor more information and installation instructions.\n\n\u003cdetails\u003e\u003csummary\u003eAlternative\u003c/summary\u003e\n\u003cp\u003e\n\nAlternatively you can create extension properties in your iOS/Apple source-set yourself:\n```kotlin\nval TimeTravelViewModel.travelEffectValue: TravelEffect?\n    get() = travelEffect.value\n```\n\u003c/p\u003e\n\u003c/details\u003e\n\n## Android\n\nUse the view model like you would any other AndroidX ViewModel:\n```kotlin\nclass TimeTravelFragment: Fragment(R.layout.fragment_time_travel) {\n    private val viewModel: TimeTravelViewModel by viewModels()\n}\n```\n\n## Swift\n\nAfter you have configured your `shared` Kotlin module and created a ViewModel it's time to configure your Swift project.  \nStart by adding the Swift package to your `Package.swift` file:\n```swift\ndependencies: [\n    .package(url: \"https://github.com/rickclephas/KMP-ObservableViewModel.git\", from: \"1.0.2\")\n]\n```\n\nOr add it in Xcode by going to `File` \u003e `Add Packages...` and providing the URL:\n`https://github.com/rickclephas/KMP-ObservableViewModel.git`.\n\n\u003cdetails\u003e\u003csummary\u003eCocoaPods\u003c/summary\u003e\n\u003cp\u003e\n\nIf you like you can also use CocoaPods instead of SPM:\n```ruby\npod 'KMPObservableViewModelSwiftUI', '1.0.2'\n```\n\u003c/p\u003e\n\u003c/details\u003e\n\nCreate a `KMPObservableViewModel.swift` file with the following contents:\n```swift\nimport KMPObservableViewModelCore\nimport shared // This should be your shared KMP module\n\nextension Kmp_observableviewmodel_coreViewModel: @retroactive ViewModel { }\n```\n\nAfter that you can use your view model almost as if it were an `ObservableObject`.   \nJust use the view model specific property wrappers and functions:\n\n| `ObservableObject`      | `ViewModel`                |\n|-------------------------|----------------------------|\n| `@StateObject`          | `@StateViewModel`          |\n| `@ObservedObject`       | `@ObservedViewModel`       |\n| `@EnvironmentObject`    | `@EnvironmentViewModel`    |\n| `environmentObject(_:)` | `environmentViewModel(_:)` |\n\nE.g. to use the `TimeTravelViewModel` as a `StateObject`:\n```swift\nimport SwiftUI\nimport KMPObservableViewModelSwiftUI\nimport shared // This should be your shared KMP module\n\nstruct ContentView: View {\n    @StateViewModel var viewModel = TimeTravelViewModel()\n}\n```\n\nIt's also possible to subclass your view model in Swift:\n```swift\nimport Combine\nimport shared // This should be your shared KMP module\n\nclass TimeTravelViewModel: shared.TimeTravelViewModel {\n    @Published var isResetDisabled: Bool = false\n}\n```\n\n### Observation support\n\nWhen using `ObservableObject`s your SwiftUI views will likely rerender a lot.\nLuckily the [Observation](https://developer.apple.com/documentation/Observation) framework brings significant \nimprovements in terms of change tracking, allowing for more efficient rerendering.\n\nTo add support for the Observation framework to all your Kotlin view models,\nsimply add the following to your `KMPObservableViewModel.swift` file:\n```swift\nimport Observation\nimport shared // This should be your shared KMP module\n\nextension Kmp_observableviewmodel_coreViewModel: @retroactive Observable { }\n```\n\nAlternatively you can add the `Observable` conformance to specific Kotlin view models instead.\n\n\u003e [!NOTE]\n\u003e The `Observable` conformance is automatically added if you subclass your view model and use the `@Observable` macro.\n\n\u003e [!NOTE]\n\u003e As an added bonus your Kotlin view models will benefit from the Observable framework on supported OS versions\n\u003e regardless of your app's deployment target. E.g. you can benefit from Observation support on iOS 17 and above\n\u003e while still supporting iOS 16 and below using `ObservableObject`s.\n\n### Child view models\n\nYou'll need some additional logic if your `ViewModel`s expose child view models.\n\nFirst make sure to use the `NativeCoroutinesRefinedState` annotation instead of the `NativeCoroutinesState` annotation:\n```kotlin\nclass MyParentViewModel: ViewModel() {\n    @NativeCoroutinesRefinedState\n    val myChildViewModel: StateFlow\u003cMyChildViewModel?\u003e = MutableStateFlow(null)\n}\n```\n\nAfter that you should create a Swift extension property using the `childViewModel(at:)` function: \n```swift\nextension MyParentViewModel {\n    var myChildViewModel: MyChildViewModel? {\n        childViewModel(at: \\.__myChildViewModel)\n    }\n}\n```\n\nThis will prevent your Swift view models from being deallocated too soon. \n\n\u003e [!NOTE]\n\u003e For lists, sets and dictionaries containing view models there is `childViewModels(at:)`.\n\n### Cancellable ViewModel\n\nWhen subclassing your Kotlin ViewModel in Swift you might experience some issues in the way those view models are cleared.\n\nAn example of such an issue is when you are using a Combine publisher to observe a Flow through KMP-NativeCoroutines:\n```swift\nimport Combine\nimport KMPNativeCoroutinesCombine\nimport shared // This should be your shared KMP module\n\nclass TimeTravelViewModel: shared.TimeTravelViewModel {\n\n    private var cancellables = Set\u003cAnyCancellable\u003e()\n\n    override init() {\n        super.init()\n        createPublisher(for: currentTimeFlow)\n            .assertNoFailure()\n            .sink { time in print(\"It's \\(time)\") }\n            .store(in: \u0026cancellables)\n    }\n}\n```\n\nSince `currentTimeFlow` is a StateFlow we don't ever expect it to fail, which is why we are using the `assertNoFailure`.\nHowever, in this case you'll notice that the publisher will fail with a `JobCancellationException`.\n\nThe problem here is that before the `TimeTravelViewModel` is deinited it will already be cleared.\nMeaning the `viewModelScope` is cancelled and `onCleared` is called.\nThis results in the Combine publisher outliving the underlying StateFlow collection.\n\nTo solve such issues you should have your Swift view model conform to `Cancellable` \nand perform the required cleanup in the `cancel` function:\n```swift\nclass TimeTravelViewModel: shared.TimeTravelViewModel, Cancellable {\n    func cancel() {\n        cancellables = []\n    }\n}\n```\n\nKMP-ObservableViewModel will make sure to call the `cancel` function before the ViewModel is being cleared.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frickclephas%2Fkmp-observableviewmodel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frickclephas%2Fkmp-observableviewmodel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frickclephas%2Fkmp-observableviewmodel/lists"}