Skip to content

Commit e2156c6

Browse files
authored
Merge branch 'mlykotom/ios-viewmodel-scoping' into kermit
2 parents 59b3ebe + 61bb0e0 commit e2156c6

File tree

8 files changed

+82
-50
lines changed

8 files changed

+82
-50
lines changed

Fruitties/iosApp/iosApp.xcodeproj/project.pbxproj

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
archiveVersion = 1;
44
classes = {
55
};
6-
objectVersion = 54;
6+
objectVersion = 70;
77
objects = {
88

99
/* Begin PBXBuildFile section */
@@ -12,8 +12,7 @@
1212
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
1313
2DBC08C52E0C3291000309C8 /* ViewModelStoreProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DBC08C42E0C3291000309C8 /* ViewModelStoreProvider.swift */; };
1414
2DBC08C72E0C32CE000309C8 /* ObservableValueWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DBC08C62E0C32CE000309C8 /* ObservableValueWrapper.swift */; };
15-
2E8773602BC85C2400BF7C40 /* CartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E87735F2BC85C2400BF7C40 /* CartView.swift */; };
16-
7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; };
15+
2DBC08C92E0C96A4000309C8 /* IOSViewModelStoreOwner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DBC08C82E0C96A4000309C8 /* IOSViewModelStoreOwner.swift */; };
1716
/* End PBXBuildFile section */
1817

1918
/* Begin PBXCopyFilesBuildPhase section */
@@ -35,12 +34,15 @@
3534
2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = "<group>"; };
3635
2DBC08C42E0C3291000309C8 /* ViewModelStoreProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelStoreProvider.swift; sourceTree = "<group>"; };
3736
2DBC08C62E0C32CE000309C8 /* ObservableValueWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableValueWrapper.swift; sourceTree = "<group>"; };
38-
2E87735F2BC85C2400BF7C40 /* CartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartView.swift; sourceTree = "<group>"; };
37+
2DBC08C82E0C96A4000309C8 /* IOSViewModelStoreOwner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOSViewModelStoreOwner.swift; sourceTree = "<group>"; };
3938
7555FF7B242A565900829871 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
40-
7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
4139
7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
4240
/* End PBXFileReference section */
4341

42+
/* Begin PBXFileSystemSynchronizedRootGroup section */
43+
2DBC08CA2E0CA0A9000309C8 /* ui */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = ui; sourceTree = "<group>"; };
44+
/* End PBXFileSystemSynchronizedRootGroup section */
45+
4446
/* Begin PBXFrameworksBuildPhase section */
4547
7555FF78242A565900829871 /* Frameworks */ = {
4648
isa = PBXFrameworksBuildPhase;
@@ -80,13 +82,13 @@
8082
7555FF7D242A565900829871 /* iosApp */ = {
8183
isa = PBXGroup;
8284
children = (
85+
2DBC08CA2E0CA0A9000309C8 /* ui */,
8386
058557BA273AAA24004C7B11 /* Assets.xcassets */,
84-
7555FF82242A565900829871 /* ContentView.swift */,
8587
7555FF8C242A565B00829871 /* Info.plist */,
8688
2152FB032600AC8F00CF470E /* iOSApp.swift */,
8789
058557D7273AAEEB004C7B11 /* Preview Content */,
88-
2E87735F2BC85C2400BF7C40 /* CartView.swift */,
8990
2DBC08C42E0C3291000309C8 /* ViewModelStoreProvider.swift */,
91+
2DBC08C82E0C96A4000309C8 /* IOSViewModelStoreOwner.swift */,
9092
2DBC08C62E0C32CE000309C8 /* ObservableValueWrapper.swift */,
9193
);
9294
path = iosApp;
@@ -116,6 +118,9 @@
116118
);
117119
dependencies = (
118120
);
121+
fileSystemSynchronizedGroups = (
122+
2DBC08CA2E0CA0A9000309C8 /* ui */,
123+
);
119124
name = iosApp;
120125
productName = iosApp;
121126
productReference = 7555FF7B242A565900829871 /* iosApp.app */;
@@ -193,9 +198,8 @@
193198
isa = PBXSourcesBuildPhase;
194199
buildActionMask = 2147483647;
195200
files = (
196-
2E8773602BC85C2400BF7C40 /* CartView.swift in Sources */,
197201
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
198-
7555FF83242A565900829871 /* ContentView.swift in Sources */,
202+
2DBC08C92E0C96A4000309C8 /* IOSViewModelStoreOwner.swift in Sources */,
199203
2DBC08C72E0C32CE000309C8 /* ObservableValueWrapper.swift in Sources */,
200204
2DBC08C52E0C3291000309C8 /* ViewModelStoreProvider.swift in Sources */,
201205
);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import SwiftUI
2+
import shared
3+
4+
/// A ViewModelStoreOwner specifically for iOS.
5+
/// This is used with from iOS with Kotlin Multiplatform (KMP).
6+
class IOSViewModelStoreOwner: ObservableObject {
7+
8+
private let viewModelStore = Lifecycle_viewmodelViewModelStore()
9+
10+
/// This function allows retrieving the androidx ViewModel from the store.
11+
func viewModel<T: AnyObject>(
12+
key: String? = nil,
13+
factory: Lifecycle_viewmodelViewModelProviderFactory,
14+
extras: Lifecycle_viewmodelCreationExtras,
15+
) -> T {
16+
let vm =
17+
viewModelStore.getViewModel(
18+
modelClass: T.self,
19+
factory: factory,
20+
key: key,
21+
extras: extras
22+
) as! T
23+
24+
return vm
25+
}
26+
27+
func clear() {
28+
viewModelStore.clear()
29+
}
30+
}

Fruitties/iosApp/iosApp/ViewModelStoreProvider.swift

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@ import shared
66
/// Manages the lifecycle of `ViewModel` instances, scoping them to this view hierarchy.
77
/// Clears the associated `ViewModelStore` when the provider disappears.
88
struct ViewModelStoreOwnerProvider<Content: View>: View {
9-
@StateObject private var viewModelStoreOwner: IOSViewModelStoreOwner =
10-
IOSViewModelStoreOwner()
9+
@StateObject private var viewModelStoreOwner = IOSViewModelStoreOwner()
1110

1211
private let content: Content
1312

1413
/// Initializes the provider with its content, creating a new `IOSViewModelStoreOwner`.
1514
init(@ViewBuilder content: () -> Content) {
16-
1715
self.content = content()
1816
}
1917

@@ -26,28 +24,4 @@ struct ViewModelStoreOwnerProvider<Content: View>: View {
2624
}
2725
}
2826

29-
/// A ViewModelStoreOwner specifically for iOS.
30-
/// This is used with from iOS with Kotlin Multiplatform (KMP).
31-
class IOSViewModelStoreOwner: ObservableObject {
32-
33-
var viewModelStore: Lifecycle_viewmodelViewModelStore =
34-
Lifecycle_viewmodelViewModelStore()
3527

36-
func viewModel<T: AnyObject>(
37-
factory: Lifecycle_viewmodelViewModelProviderFactory,
38-
extras: Lifecycle_viewmodelCreationExtras,
39-
) -> T {
40-
let vm =
41-
viewModelStore.getViewModel(
42-
modelClass: T.self,
43-
factory: factory,
44-
extras: extras
45-
) as! T
46-
47-
return vm
48-
}
49-
50-
func clear() {
51-
viewModelStore.clear()
52-
}
53-
}

Fruitties/iosApp/iosApp/ContentView.swift renamed to Fruitties/iosApp/iosApp/ui/ContentView.swift

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17+
import Foundation
1718
import SwiftUI
1819
import shared
19-
import Foundation
2020

2121
struct ContentView: View {
2222
/// Injects the `IOSViewModelStoreOwner` from the environment, which manages the lifecycle of `ViewModel` instances.
@@ -37,7 +37,7 @@ struct ContentView: View {
3737
VStack {
3838
Text("Fruitties").font(.largeTitle).fontWeight(.bold)
3939
NavigationLink {
40-
ViewModelStoreOwnerProvider{
40+
ViewModelStoreOwnerProvider {
4141
CartView()
4242
}
4343
} label: {
@@ -49,12 +49,18 @@ struct ContentView: View {
4949
Observing(mainViewModel.homeUiState) { homeUIState in
5050
ScrollView {
5151
LazyVStack {
52-
ForEach(homeUIState.fruitties, id: \.self) { value in
53-
FruittieView(fruittie: value, addToCart: { fruittie in
54-
Task {
55-
mainViewModel.addItemToCart(fruittie: fruittie)
52+
ForEach(homeUIState.fruitties, id: \.self) {
53+
value in
54+
FruittieView(
55+
fruittie: value,
56+
addToCart: { fruittie in
57+
Task {
58+
mainViewModel.addItemToCart(
59+
fruittie: fruittie
60+
)
61+
}
5662
}
57-
})
63+
)
5864
}
5965
}
6066
}
@@ -70,7 +76,9 @@ struct FruittieView: View {
7076
var body: some View {
7177
HStack(alignment: .firstTextBaseline) {
7278
ZStack {
73-
RoundedRectangle(cornerRadius: 15).fill(Color(red: 0.8, green: 0.8, blue: 1.0))
79+
RoundedRectangle(cornerRadius: 15).fill(
80+
Color(red: 0.8, green: 0.8, blue: 1.0)
81+
)
7482
VStack {
7583
Text("\(fruittie.name)")
7684
.fontWeight(.bold)
@@ -79,9 +87,12 @@ struct FruittieView: View {
7987
.frame(maxWidth: .infinity, alignment: .leading)
8088
}.padding()
8189
Spacer()
82-
Button(action: { addToCart(fruittie) }, label: {
83-
Text("Add")
84-
}).padding().frame(maxWidth: .infinity, alignment: .trailing)
90+
Button(
91+
action: { addToCart(fruittie) },
92+
label: {
93+
Text("Add")
94+
}
95+
).padding().frame(maxWidth: .infinity, alignment: .trailing)
8596
}.padding([.leading, .trailing])
8697
}
8798
}

Fruitties/shared/src/commonMain/kotlin/com/example/fruitties/viewmodel/CartViewModel.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class CartViewModel(
3131
private val repository: DataRepository,
3232
) : ViewModel() {
3333

34+
<<<<<<< kermit
3435
init {
3536
Logger.i { "CartViewModel created" }
3637
}
@@ -40,6 +41,8 @@ class CartViewModel(
4041
Logger.i { "CartViewModel cleared" }
4142
}
4243

44+
=======
45+
>>>>>>> mlykotom/ios-viewmodel-scoping
4346
val cartUiState: StateFlow<CartUiState> =
4447
repository.cartDetails
4548
.map { details ->

Fruitties/shared/src/commonMain/kotlin/com/example/fruitties/viewmodel/ViewModelFactory.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ fun creationExtras(appContainer: AppContainer): CreationExtras =
2525
set(APP_CONTAINER_KEY, appContainer)
2626
}
2727

28+
fun creationExtras(
29+
appContainer: AppContainer,
30+
additional: MutableCreationExtras.() -> Unit
31+
): CreationExtras =
32+
MutableCreationExtras().apply {
33+
set(APP_CONTAINER_KEY, appContainer)
34+
additional()
35+
}
36+
2837
inline fun <reified T : ViewModel> vmFactory(
2938
crossinline initializer: CreationExtras.(AppContainer) -> T
3039
) =

Fruitties/shared/src/iosMain/kotlin/com/example/fruitties/di/viewmodel/IOSViewModelStoreOwner.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ import kotlin.reflect.KClass
1414
fun ViewModelStore.getViewModel(
1515
modelClass: ObjCClass,
1616
factory: ViewModelProvider.Factory,
17+
key: String?,
1718
extras: CreationExtras,
1819
): ViewModel {
1920
@Suppress("UNCHECKED_CAST")
20-
val vmClass = (getOriginalKotlinClass(modelClass) as? KClass<ViewModel>)
21+
val vmClass = getOriginalKotlinClass(modelClass) as? KClass<ViewModel>
2122
?: error("modelClass isn't a ViewModel type")
2223
val provider = ViewModelProvider.create(this, factory, extras)
23-
return provider[vmClass]
24-
}
24+
return key?.let { provider[key, vmClass] } ?: provider[vmClass]
25+
}

0 commit comments

Comments
 (0)