Skip to content

Commit c547df5

Browse files
committed
Initially copy and adapt more ViewModel code from Compose Multiplatform
The added code in the "common" module is temporarily commented out to see whether it's necessary. Under such a condition, an exception is thrown with the message "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner" on JS DOM in an internal app but not in the demo.
1 parent ebfa280 commit c547df5

File tree

7 files changed

+223
-5
lines changed

7 files changed

+223
-5
lines changed

common/build.gradle.kts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ kotlin {
5656
// see: https://github.com/varabyte/kobweb/blob/main/frontend/kobweb-compose/build.gradle.kts
5757
api("com.varabyte.kobweb:kobweb-compose:${DependencyVersions.kobweb}")
5858
implementation("com.huanshankeji:compose-html-common:${DependencyVersions.huanshankejiComposeHtml}")
59+
60+
// TODO not used yet
61+
/*
62+
The UI module depends on the lifecycle module to use `androidx.lifecycle.ViewModelStoreOwner`.
63+
See https://github.com/JetBrains/compose-multiplatform-core/blob/jb-main/compose/ui/ui/build.gradle#L87.
64+
This is actually only needed for JS DOM.
65+
*/
66+
//implementation(cpnProject(project, ":lifecycle-viewmodel"))
5967
}
6068
}
6169
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.huanshankeji.compose.ui.platform
2+
3+
/*
4+
// copied and adapted from "DefaultViewModelOwnerStore.skiko.kt" in `androidx.compose.ui.platform`
5+
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.runtime.InternalComposeApi
8+
import androidx.compose.runtime.staticCompositionLocalOf
9+
import androidx.lifecycle.ViewModelStoreOwner
10+
11+
/**
12+
* Internal helper to provide [ViewModelStoreOwner] from Compose UI module.
13+
* In applications please use [androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner].
14+
*
15+
* @hide
16+
*/
17+
internal val LocalInternalViewModelStoreOwner = staticCompositionLocalOf<ViewModelStoreOwner?> {
18+
null
19+
}
20+
21+
@InternalComposeApi
22+
@Composable
23+
fun findComposeDefaultViewModelStoreOwner(): ViewModelStoreOwner? =
24+
LocalInternalViewModelStoreOwner.current
25+
*/
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.huanshankeji.compose.ui.window
2+
3+
/*
4+
import androidx.compose.runtime.Composable
5+
import androidx.compose.runtime.Composition
6+
import androidx.compose.runtime.CompositionLocalProvider
7+
import com.huanshankeji.compose.ui.platform.LocalInternalViewModelStoreOwner
8+
import org.jetbrains.compose.web.dom.DOMScope
9+
import org.jetbrains.compose.web.renderComposableInBody
10+
import org.w3c.dom.HTMLBodyElement
11+
12+
// TODO not used yet so made private
13+
private fun renderComposableInBodyWithLifecycle(
14+
content: @Composable DOMScope<HTMLBodyElement>.() -> Unit
15+
): Composition =
16+
renderComposableInBody {
17+
// copied and adapted from `ComposeWindow` in "ComposeWindow.web.kt" in `androidx.compose.ui.window`
18+
// also see `ComposeViewport` on Wasm JS
19+
CompositionLocalProvider(
20+
//LocalSystemTheme provides systemThemeObserver.currentSystemTheme.value, // TODO add back if needed one day
21+
//LocalLifecycleOwner provides this, // TODO
22+
LocalInternalViewModelStoreOwner provides TODO(),
23+
content = {
24+
content()
25+
}
26+
)
27+
}
28+
*/
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,56 @@
11
package com.huanshankeji.androidx.lifecycle.viewmodel.compose
22

33
import androidx.compose.runtime.Composable
4+
import androidx.lifecycle.HasDefaultViewModelProviderFactory
45
import androidx.lifecycle.ViewModel
6+
import androidx.lifecycle.ViewModelProvider
7+
import androidx.lifecycle.ViewModelStoreOwner
58
import androidx.lifecycle.viewmodel.CreationExtras
9+
import kotlin.reflect.KClass
610

711
// https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-viewmodel.html
812

13+
// copied and adapted from "ViewModel.kt" in `androidx.lifecycle.viewmodel.compose`
14+
15+
16+
// `expect` can be removed if `expect object LocalViewModelStoreOwner` is added.
17+
@PublishedApi
18+
@Composable
19+
internal expect fun defaultViewModelStoreOwner(): ViewModelStoreOwner
20+
21+
@PublishedApi
22+
internal fun ViewModelStoreOwner.defaultCreationExtras(): CreationExtras =
23+
if (this is HasDefaultViewModelProviderFactory) {
24+
this.defaultViewModelCreationExtras
25+
} else {
26+
CreationExtras.Empty
27+
}
28+
29+
30+
@Composable
31+
expect fun <VM : ViewModel> viewModel(
32+
modelClass: KClass<VM>,
33+
viewModelStoreOwner: ViewModelStoreOwner = defaultViewModelStoreOwner(),
34+
key: String? = null,
35+
factory: ViewModelProvider.Factory? = null,
36+
extras: CreationExtras = viewModelStoreOwner.defaultCreationExtras()
37+
): VM
38+
939
@Composable
1040
expect inline fun <reified VM : ViewModel> viewModel(
41+
viewModelStoreOwner: ViewModelStoreOwner = defaultViewModelStoreOwner(),
1142
key: String? = null,
1243
noinline initializer: CreationExtras.() -> VM
1344
): VM
45+
46+
@Deprecated(
47+
"Use the one with a `viewModelStoreOwner` parameter instead. " +
48+
"This function might be removed in the future. " +
49+
"Make sure you call this function with named arguments please so your source still compile when this is removed."
50+
)
51+
@Composable
52+
inline fun <reified VM : ViewModel> viewModel(
53+
key: String? = null,
54+
noinline initializer: CreationExtras.() -> VM
55+
): VM =
56+
viewModel(defaultViewModelStoreOwner(), key, initializer)

lifecycle-viewmodel/src/composeUiMain/kotlin/ViewModel.composeUi.kt

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,33 @@ package com.huanshankeji.androidx.lifecycle.viewmodel.compose
22

33
import androidx.compose.runtime.Composable
44
import androidx.lifecycle.ViewModel
5+
import androidx.lifecycle.ViewModelProvider
6+
import androidx.lifecycle.ViewModelStoreOwner
57
import androidx.lifecycle.viewmodel.CreationExtras
6-
import androidx.lifecycle.viewmodel.compose.viewModel
8+
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
9+
import kotlin.reflect.KClass
10+
import androidx.lifecycle.viewmodel.compose.viewModel as composeUiViewModel
711

12+
// copied and adapted from "ViewModel.kt" in `androidx.lifecycle.viewmodel.compose`
13+
@PublishedApi
814
@Composable
9-
actual inline fun <reified VM : ViewModel> viewModel(key: String?, noinline initializer: CreationExtras.() -> VM): VM =
10-
viewModel(key = key, initializer = initializer)
15+
internal actual fun defaultViewModelStoreOwner(): ViewModelStoreOwner =
16+
checkNotNull(LocalViewModelStoreOwner.current) {
17+
"No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
18+
}
19+
20+
@Composable
21+
actual fun <VM : ViewModel> viewModel(
22+
modelClass: KClass<VM>,
23+
viewModelStoreOwner: ViewModelStoreOwner,
24+
key: String?,
25+
factory: ViewModelProvider.Factory?,
26+
extras: CreationExtras
27+
): VM =
28+
composeUiViewModel(modelClass, viewModelStoreOwner, key, factory, extras)
29+
30+
@Composable
31+
actual inline fun <reified VM : ViewModel> viewModel(
32+
viewModelStoreOwner: ViewModelStoreOwner, key: String?, noinline initializer: CreationExtras.() -> VM
33+
): VM =
34+
composeUiViewModel(viewModelStoreOwner, key, initializer)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.huanshankeji.androidx.lifecycle.viewmodel.compose
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.ProvidedValue
5+
import androidx.compose.runtime.compositionLocalOf
6+
import androidx.lifecycle.ViewModelStoreOwner
7+
8+
// copied and adapted from "LocalViewModelStoreOwner.kt" and "LocalViewModelStoreOwner.jb.kt" in `androidx.lifecycle.viewmodel.compose`
9+
10+
object LocalViewModelStoreOwner {
11+
private val LocalViewModelStoreOwner =
12+
compositionLocalOf<ViewModelStoreOwner?> { null }
13+
val current: ViewModelStoreOwner?
14+
@Composable
15+
get() = LocalViewModelStoreOwner.current ?: findViewTreeViewModelStoreOwner()
16+
17+
infix fun provides(viewModelStoreOwner: ViewModelStoreOwner):
18+
ProvidedValue<ViewModelStoreOwner?> {
19+
return LocalViewModelStoreOwner.provides(viewModelStoreOwner)
20+
}
21+
}
22+
23+
@Composable
24+
internal fun findViewTreeViewModelStoreOwner(): ViewModelStoreOwner? =
25+
// TODO
26+
null //findComposeDefaultViewModelStoreOwner()
Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,74 @@
11
package com.huanshankeji.androidx.lifecycle.viewmodel.compose
22

33
import androidx.compose.runtime.Composable
4-
import androidx.compose.runtime.remember
4+
import androidx.lifecycle.HasDefaultViewModelProviderFactory
55
import androidx.lifecycle.ViewModel
6+
import androidx.lifecycle.ViewModelProvider
7+
import androidx.lifecycle.ViewModelStoreOwner
68
import androidx.lifecycle.viewmodel.CreationExtras
9+
import androidx.lifecycle.viewmodel.initializer
10+
import androidx.lifecycle.viewmodel.viewModelFactory
11+
import kotlin.reflect.KClass
12+
13+
// copied and adapted from "ViewModel.kt" in `androidx.lifecycle.viewmodel.compose`
14+
15+
16+
17+
@PublishedApi
18+
@Composable
19+
internal actual fun defaultViewModelStoreOwner(): ViewModelStoreOwner =
20+
checkNotNull(LocalViewModelStoreOwner.current) {
21+
"No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
22+
}
23+
724

825
@Composable
9-
actual inline fun <reified VM : ViewModel> viewModel(key: String?, noinline initializer: CreationExtras.() -> VM): VM =
26+
actual fun <VM : ViewModel> viewModel(
27+
modelClass: KClass<VM>,
28+
viewModelStoreOwner: ViewModelStoreOwner,
29+
key: String?,
30+
factory: ViewModelProvider.Factory?,
31+
extras: CreationExtras
32+
): VM = viewModelStoreOwner.get(modelClass, key, factory, extras)
33+
34+
@Composable
35+
actual inline fun <reified VM : ViewModel> viewModel(
36+
viewModelStoreOwner: ViewModelStoreOwner,
37+
key: String?,
38+
noinline initializer: CreationExtras.() -> VM
39+
): VM = viewModel(
40+
VM::class,
41+
viewModelStoreOwner,
42+
key,
43+
viewModelFactory { initializer(initializer) },
44+
viewModelStoreOwner.defaultCreationExtras()
45+
)
46+
47+
// TODO remove
48+
/*
49+
@Composable
50+
actual inline fun <reified VM : ViewModel> viewModel(
51+
viewModelStoreOwner: ViewModelStoreOwner, key: String?, noinline initializer: CreationExtras.() -> VM
52+
): VM =
1053
remember(key) { CreationExtras.Empty.initializer() }
54+
*/
55+
56+
internal fun <VM : ViewModel> ViewModelStoreOwner.get(
57+
modelClass: KClass<VM>,
58+
key: String?,
59+
factory: ViewModelProvider.Factory?,
60+
extras: CreationExtras
61+
): VM {
62+
val provider = if (factory != null) {
63+
ViewModelProvider.create(this.viewModelStore, factory, extras)
64+
} else if (this is HasDefaultViewModelProviderFactory) {
65+
ViewModelProvider.create(this.viewModelStore, this.defaultViewModelProviderFactory, extras)
66+
} else {
67+
ViewModelProvider.create(this)
68+
}
69+
return if (key != null) {
70+
provider[key, modelClass]
71+
} else {
72+
provider[modelClass]
73+
}
74+
}

0 commit comments

Comments
 (0)