Skip to content

Commit fcfc88a

Browse files
committed
Merge branch 'master' into feature/coroutines-1.7.0
2 parents 25a51e6 + 46f1a15 commit fcfc88a

13 files changed

Lines changed: 86 additions & 35 deletions

File tree

.idea/kotlinc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

KMMViewModelCore.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'KMMViewModelCore'
3-
s.version = '1.0.0-ALPHA-6'
3+
s.version = '1.0.0-ALPHA-7'
44
s.summary = 'Library to share Kotlin ViewModels with Swift'
55

66
s.homepage = 'https://github.com/rickclephas/KMM-ViewModel'

KMMViewModelCore/ObservableViewModel.swift

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,26 @@
88
import Combine
99
import KMMViewModelCoreObjC
1010

11-
/// Creates an `ObservableObject` for the specified `KMMViewModel`.
12-
/// - Parameter viewModel: The `KMMViewModel` to wrap in an `ObservableObject`.
11+
@available(*, deprecated, renamed: "observableViewModel")
1312
public func createObservableViewModel<ViewModel: KMMViewModel>(
1413
for viewModel: ViewModel
1514
) -> ObservableViewModel<ViewModel> {
16-
ObservableViewModel(viewModel)
15+
observableViewModel(for: viewModel)
16+
}
17+
18+
private var observableViewModelKey = "observableViewModel"
19+
20+
/// Gets an `ObservableObject` for the specified `KMMViewModel`.
21+
/// - Parameter viewModel: The `KMMViewModel` to wrap in an `ObservableObject`.
22+
public func observableViewModel<ViewModel: KMMViewModel>(
23+
for viewModel: ViewModel
24+
) -> ObservableViewModel<ViewModel> {
25+
if let observableViewModel = objc_getAssociatedObject(viewModel, &observableViewModelKey) {
26+
return observableViewModel as! ObservableViewModel<ViewModel>
27+
}
28+
let observableViewModel = ObservableViewModel(viewModel)
29+
objc_setAssociatedObject(viewModel, &observableViewModelKey, observableViewModel, .OBJC_ASSOCIATION_ASSIGN)
30+
return observableViewModel
1731
}
1832

1933
/// An `ObservableObject` for a `KMMViewModel`.

KMMViewModelCoreObjC.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'KMMViewModelCoreObjC'
3-
s.version = '1.0.0-ALPHA-6'
3+
s.version = '1.0.0-ALPHA-7'
44
s.summary = 'Library to share Kotlin ViewModels with Swift'
55

66
s.homepage = 'https://github.com/rickclephas/KMM-ViewModel'

KMMViewModelSwiftUI.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'KMMViewModelSwiftUI'
3-
s.version = '1.0.0-ALPHA-6'
3+
s.version = '1.0.0-ALPHA-7'
44
s.summary = 'Library to share Kotlin ViewModels with SwiftUI'
55

66
s.homepage = 'https://github.com/rickclephas/KMM-ViewModel'

KMMViewModelSwiftUI/EnvironmentViewModel.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,10 @@ public extension View {
3131
/// Supplies a `KMMViewModel` to a view subhierarchy.
3232
/// - Parameter viewModel: The `KMMViewModel` to supply to a view subhierarchy.
3333
func environmentViewModel<ViewModel: KMMViewModel>(_ viewModel: ViewModel) -> some View {
34-
environmentObject(createObservableViewModel(for: viewModel))
34+
environmentObject(observableViewModel(for: viewModel))
3535
}
3636

37-
/// Supplies a `KMMViewModel` to a view subhierarchy.
38-
/// - Parameter projectedValue: The `projectedValue` from e.g. `StateViewModel`.
37+
@available(*, deprecated)
3938
func environmentViewModel<ViewModel: KMMViewModel>(_ projectedValue: ObservableViewModel<ViewModel>.Projection) -> some View {
4039
environmentObject(projectedValue.observableObject)
4140
}

KMMViewModelSwiftUI/ObservedViewModel.swift

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,20 @@ public struct ObservedViewModel<ViewModel: KMMViewModel>: DynamicProperty {
1515

1616
@ObservedObject private var observableObject: ObservableViewModel<ViewModel>
1717

18-
/// The underlying `KMMViewModel` referenced by the `ObservedViewModel`.
19-
public var wrappedValue: ViewModel { observableObject.viewModel }
20-
2118
/// A projection of the observed `KMMViewModel` that creates bindings to its properties using dynamic member lookup.
2219
public var projectedValue: ObservableViewModel<ViewModel>.Projection
2320

24-
/// Creates an `ObservedViewModel` for the specified `KMMViewModel` projection.
25-
/// - Parameter projectedValue: The `projectedValue` from e.g. `StateViewModel`.
21+
/// The underlying `KMMViewModel` referenced by the `ObservedViewModel`.
22+
public var wrappedValue: ViewModel {
23+
get { observableObject.viewModel }
24+
set {
25+
let observableObject = observableViewModel(for: newValue)
26+
self.observableObject = observableObject
27+
self.projectedValue = ObservableViewModel.Projection(observableObject)
28+
}
29+
}
30+
31+
@available(*, deprecated)
2632
public init(_ projectedValue: ObservableViewModel<ViewModel>.Projection) {
2733
self.observableObject = projectedValue.observableObject
2834
self.projectedValue = projectedValue
@@ -31,7 +37,7 @@ public struct ObservedViewModel<ViewModel: KMMViewModel>: DynamicProperty {
3137
/// Creates an `ObservedViewModel` for the specified `KMMViewModel`.
3238
/// - Parameter wrappedValue: The `KMMViewModel` to observe.
3339
public init(wrappedValue: ViewModel) {
34-
let observableObject = createObservableViewModel(for: wrappedValue)
40+
let observableObject = observableViewModel(for: wrappedValue)
3541
self.observableObject = observableObject
3642
self.projectedValue = ObservableViewModel.Projection(observableObject)
3743
}

KMMViewModelSwiftUI/StateViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@ public struct StateViewModel<ViewModel: KMMViewModel>: DynamicProperty {
2727
/// Creates a `StateViewModel` for the specified `KMMViewModel`.
2828
/// - Parameter wrappedValue: The `KMMViewModel` to observe.
2929
public init(wrappedValue: @autoclosure @escaping () -> ViewModel) {
30-
self._observableObject = StateObject(wrappedValue: createObservableViewModel(for: wrappedValue()))
30+
self._observableObject = StateObject(wrappedValue: observableViewModel(for: wrappedValue()))
3131
}
3232
}

README.md

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ A library that allows you to share ViewModels between Android and iOS.
44

55
## Compatibility
66

7-
The latest version of the library uses Kotlin version `1.8.20`.
7+
The latest version of the library uses Kotlin version `1.8.21`.
88
Compatibility versions for newer Kotlin versions are also available:
99

1010
| Version | Version suffix | Kotlin | Coroutines |
1111
|---------------|-----------------|:----------:|:----------:|
12-
| **_latest_** | **_no suffix_** | **1.8.20** | **1.6.4** |
12+
| **_latest_** | **_no suffix_** | **1.8.21** | **1.6.4** |
13+
| 1.0.0-ALPHA-6 | _no suffix_ | 1.8.20 | 1.6.4 |
1314
| 1.0.0-ALPHA-4 | _no suffix_ | 1.8.10 | 1.6.4 |
1415
| 1.0.0-ALPHA-3 | _no suffix_ | 1.8.0 | 1.6.4 |
1516

@@ -22,13 +23,12 @@ dependencies {
2223
}
2324
```
2425

25-
Create your ViewModels almost as you would in Android:
26+
And create your ViewModels:
2627
```kotlin
2728
import com.rickclephas.kmm.viewmodel.KMMViewModel
2829
import com.rickclephas.kmm.viewmodel.MutableStateFlow
2930
import com.rickclephas.kmm.viewmodel.stateIn
3031

31-
// 1: use KMMViewModel instead of ViewModel
3232
open class TimeTravelViewModel: KMMViewModel() {
3333

3434
private val clockTime = Clock.time
@@ -37,10 +37,8 @@ open class TimeTravelViewModel: KMMViewModel() {
3737
* A [StateFlow] that emits the actual time.
3838
*/
3939
val actualTime = clockTime.map { formatTime(it) }
40-
// 2: supply viewModelScope to your stateIn calls
4140
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "N/A")
4241

43-
// 3: supply viewModelScope to your MutableStateFlows
4442
private val _travelEffect = MutableStateFlow<TravelEffect?>(viewModelScope, null)
4543
/**
4644
* A [StateFlow] that emits the applied [TravelEffect].
@@ -49,21 +47,55 @@ open class TimeTravelViewModel: KMMViewModel() {
4947
}
5048
```
5149

52-
You need to use `viewModelScope` wherever possible to propagate state changes to iOS.
53-
In other cases you can access the `ViewModelScope.coroutineScope` property directly.
50+
As you might notice it isn't much different from an Android ViewModel.
51+
The most obvious difference is the `KMMViewModel` superclass:
52+
53+
```diff
54+
- import androidx.lifecycle.ViewModel
55+
+ import com.rickclephas.kmm.viewmodel.KMMViewModel
56+
57+
- open class TimeTravelViewModel: ViewModel() {
58+
+ open class TimeTravelViewModel: KMMViewModel() {
59+
```
60+
61+
Besides that there are only 2 minor differences.
62+
The first being a different import for `stateIn`:
63+
64+
```diff
65+
- import kotlinx.coroutines.flow.stateIn
66+
+ import com.rickclephas.kmm.viewmodel.stateIn
67+
68+
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "N/A")
69+
```
70+
71+
And the second being a different `MutableStateFlow` constructor:
72+
73+
```diff
74+
- import kotlinx.coroutines.flow.MutableStateFlow
75+
+ import com.rickclephas.kmm.viewmodel.MutableStateFlow
76+
77+
- private val _travelEffect = MutableStateFlow<TravelEffect?>(null)
78+
+ private val _travelEffect = MutableStateFlow<TravelEffect?>(viewModelScope, null)
79+
```
80+
81+
These minor differences will make sure that any state changes are propagate to iOS.
82+
83+
> **Note**: `viewModelScope` is a wrapper around the actual `CoroutineScope` which can be accessed
84+
> via the `ViewModelScope.coroutineScope` property.
5485
5586
### KMP-NativeCoroutines
5687

57-
Use the `@NativeCoroutinesState` annotation from [KMP-NativeCoroutines](https://github.com/rickclephas/KMP-NativeCoroutines)
88+
I highly recommend you to use the `@NativeCoroutinesState` annotation from
89+
[KMP-NativeCoroutines](https://github.com/rickclephas/KMP-NativeCoroutines)
5890
to turn your `StateFlow`s into properties in Swift:
5991

6092
```kotlin
6193
@NativeCoroutinesState
6294
val travelEffect = _travelEffect.asStateFlow()
6395
```
6496

65-
Checkout the [README](https://github.com/rickclephas/KMP-NativeCoroutines/blob/master/README.md)
66-
for more information and installation instructions for KMP-NativeCoroutines.
97+
Checkout the KMP-NativeCoroutines [README](https://github.com/rickclephas/KMP-NativeCoroutines/blob/master/README.md)
98+
for more information and installation instructions.
6799

68100
<details><summary>Alternative</summary>
69101
<p>
@@ -92,7 +124,7 @@ class TimeTravelFragment: Fragment(R.layout.fragment_time_travel) {
92124
}
93125
```
94126

95-
> **Note:** support for Jetpack Compose is coming soon.
127+
> **Note:** improved support for Jetpack Compose is coming soon.
96128
97129
## Swift
98130

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ buildscript {
88

99
allprojects {
1010
group = "com.rickclephas.kmm"
11-
version = "1.0.0-ALPHA-6"
11+
version = "1.0.0-ALPHA-7"
1212

1313
repositories {
1414
mavenCentral()

0 commit comments

Comments
 (0)