Skip to content

Commit

Permalink
Initially copy and adapt more ViewModel code from Compose Multiplatform
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ShreckYe committed Jan 7, 2025
1 parent ebfa280 commit c547df5
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 5 deletions.
8 changes: 8 additions & 0 deletions common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ kotlin {
// see: https://github.com/varabyte/kobweb/blob/main/frontend/kobweb-compose/build.gradle.kts
api("com.varabyte.kobweb:kobweb-compose:${DependencyVersions.kobweb}")
implementation("com.huanshankeji:compose-html-common:${DependencyVersions.huanshankejiComposeHtml}")

// TODO not used yet
/*
The UI module depends on the lifecycle module to use `androidx.lifecycle.ViewModelStoreOwner`.
See https://github.com/JetBrains/compose-multiplatform-core/blob/jb-main/compose/ui/ui/build.gradle#L87.
This is actually only needed for JS DOM.
*/
//implementation(cpnProject(project, ":lifecycle-viewmodel"))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.huanshankeji.compose.ui.platform

/*
// copied and adapted from "DefaultViewModelOwnerStore.skiko.kt" in `androidx.compose.ui.platform`
import androidx.compose.runtime.Composable
import androidx.compose.runtime.InternalComposeApi
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.lifecycle.ViewModelStoreOwner
/**
* Internal helper to provide [ViewModelStoreOwner] from Compose UI module.
* In applications please use [androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner].
*
* @hide
*/
internal val LocalInternalViewModelStoreOwner = staticCompositionLocalOf<ViewModelStoreOwner?> {
null
}
@InternalComposeApi
@Composable
fun findComposeDefaultViewModelStoreOwner(): ViewModelStoreOwner? =
LocalInternalViewModelStoreOwner.current
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.huanshankeji.compose.ui.window

/*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composition
import androidx.compose.runtime.CompositionLocalProvider
import com.huanshankeji.compose.ui.platform.LocalInternalViewModelStoreOwner
import org.jetbrains.compose.web.dom.DOMScope
import org.jetbrains.compose.web.renderComposableInBody
import org.w3c.dom.HTMLBodyElement
// TODO not used yet so made private
private fun renderComposableInBodyWithLifecycle(
content: @Composable DOMScope<HTMLBodyElement>.() -> Unit
): Composition =
renderComposableInBody {
// copied and adapted from `ComposeWindow` in "ComposeWindow.web.kt" in `androidx.compose.ui.window`
// also see `ComposeViewport` on Wasm JS
CompositionLocalProvider(
//LocalSystemTheme provides systemThemeObserver.currentSystemTheme.value, // TODO add back if needed one day
//LocalLifecycleOwner provides this, // TODO
LocalInternalViewModelStoreOwner provides TODO(),
content = {
content()
}
)
}
*/
43 changes: 43 additions & 0 deletions lifecycle-viewmodel/src/commonMain/kotlin/ViewModel.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,56 @@
package com.huanshankeji.androidx.lifecycle.viewmodel.compose

import androidx.compose.runtime.Composable
import androidx.lifecycle.HasDefaultViewModelProviderFactory
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.viewmodel.CreationExtras
import kotlin.reflect.KClass

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

// copied and adapted from "ViewModel.kt" in `androidx.lifecycle.viewmodel.compose`


// `expect` can be removed if `expect object LocalViewModelStoreOwner` is added.
@PublishedApi
@Composable
internal expect fun defaultViewModelStoreOwner(): ViewModelStoreOwner

@PublishedApi
internal fun ViewModelStoreOwner.defaultCreationExtras(): CreationExtras =
if (this is HasDefaultViewModelProviderFactory) {
this.defaultViewModelCreationExtras
} else {
CreationExtras.Empty
}


@Composable
expect fun <VM : ViewModel> viewModel(
modelClass: KClass<VM>,
viewModelStoreOwner: ViewModelStoreOwner = defaultViewModelStoreOwner(),
key: String? = null,
factory: ViewModelProvider.Factory? = null,
extras: CreationExtras = viewModelStoreOwner.defaultCreationExtras()
): VM

@Composable
expect inline fun <reified VM : ViewModel> viewModel(
viewModelStoreOwner: ViewModelStoreOwner = defaultViewModelStoreOwner(),
key: String? = null,
noinline initializer: CreationExtras.() -> VM
): VM

@Deprecated(
"Use the one with a `viewModelStoreOwner` parameter instead. " +
"This function might be removed in the future. " +
"Make sure you call this function with named arguments please so your source still compile when this is removed."
)
@Composable
inline fun <reified VM : ViewModel> viewModel(
key: String? = null,
noinline initializer: CreationExtras.() -> VM
): VM =
viewModel(defaultViewModelStoreOwner(), key, initializer)
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,33 @@ package com.huanshankeji.androidx.lifecycle.viewmodel.compose

import androidx.compose.runtime.Composable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import kotlin.reflect.KClass
import androidx.lifecycle.viewmodel.compose.viewModel as composeUiViewModel

// copied and adapted from "ViewModel.kt" in `androidx.lifecycle.viewmodel.compose`
@PublishedApi
@Composable
actual inline fun <reified VM : ViewModel> viewModel(key: String?, noinline initializer: CreationExtras.() -> VM): VM =
viewModel(key = key, initializer = initializer)
internal actual fun defaultViewModelStoreOwner(): ViewModelStoreOwner =
checkNotNull(LocalViewModelStoreOwner.current) {
"No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
}

@Composable
actual fun <VM : ViewModel> viewModel(
modelClass: KClass<VM>,
viewModelStoreOwner: ViewModelStoreOwner,
key: String?,
factory: ViewModelProvider.Factory?,
extras: CreationExtras
): VM =
composeUiViewModel(modelClass, viewModelStoreOwner, key, factory, extras)

@Composable
actual inline fun <reified VM : ViewModel> viewModel(
viewModelStoreOwner: ViewModelStoreOwner, key: String?, noinline initializer: CreationExtras.() -> VM
): VM =
composeUiViewModel(viewModelStoreOwner, key, initializer)
26 changes: 26 additions & 0 deletions lifecycle-viewmodel/src/jsMain/kotlin/LocalViewModelStoreOwner.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.huanshankeji.androidx.lifecycle.viewmodel.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.ProvidedValue
import androidx.compose.runtime.compositionLocalOf
import androidx.lifecycle.ViewModelStoreOwner

// copied and adapted from "LocalViewModelStoreOwner.kt" and "LocalViewModelStoreOwner.jb.kt" in `androidx.lifecycle.viewmodel.compose`

object LocalViewModelStoreOwner {
private val LocalViewModelStoreOwner =
compositionLocalOf<ViewModelStoreOwner?> { null }
val current: ViewModelStoreOwner?
@Composable
get() = LocalViewModelStoreOwner.current ?: findViewTreeViewModelStoreOwner()

infix fun provides(viewModelStoreOwner: ViewModelStoreOwner):
ProvidedValue<ViewModelStoreOwner?> {
return LocalViewModelStoreOwner.provides(viewModelStoreOwner)
}
}

@Composable
internal fun findViewTreeViewModelStoreOwner(): ViewModelStoreOwner? =
// TODO
null //findComposeDefaultViewModelStoreOwner()
68 changes: 66 additions & 2 deletions lifecycle-viewmodel/src/jsMain/kotlin/ViewModel.js.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,74 @@
package com.huanshankeji.androidx.lifecycle.viewmodel.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.lifecycle.HasDefaultViewModelProviderFactory
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import kotlin.reflect.KClass

// copied and adapted from "ViewModel.kt" in `androidx.lifecycle.viewmodel.compose`



@PublishedApi
@Composable
internal actual fun defaultViewModelStoreOwner(): ViewModelStoreOwner =
checkNotNull(LocalViewModelStoreOwner.current) {
"No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
}


@Composable
actual inline fun <reified VM : ViewModel> viewModel(key: String?, noinline initializer: CreationExtras.() -> VM): VM =
actual fun <VM : ViewModel> viewModel(
modelClass: KClass<VM>,
viewModelStoreOwner: ViewModelStoreOwner,
key: String?,
factory: ViewModelProvider.Factory?,
extras: CreationExtras
): VM = viewModelStoreOwner.get(modelClass, key, factory, extras)

@Composable
actual inline fun <reified VM : ViewModel> viewModel(
viewModelStoreOwner: ViewModelStoreOwner,
key: String?,
noinline initializer: CreationExtras.() -> VM
): VM = viewModel(
VM::class,
viewModelStoreOwner,
key,
viewModelFactory { initializer(initializer) },
viewModelStoreOwner.defaultCreationExtras()
)

// TODO remove
/*
@Composable
actual inline fun <reified VM : ViewModel> viewModel(
viewModelStoreOwner: ViewModelStoreOwner, key: String?, noinline initializer: CreationExtras.() -> VM
): VM =
remember(key) { CreationExtras.Empty.initializer() }
*/

internal fun <VM : ViewModel> ViewModelStoreOwner.get(
modelClass: KClass<VM>,
key: String?,
factory: ViewModelProvider.Factory?,
extras: CreationExtras
): VM {
val provider = if (factory != null) {
ViewModelProvider.create(this.viewModelStore, factory, extras)
} else if (this is HasDefaultViewModelProviderFactory) {
ViewModelProvider.create(this.viewModelStore, this.defaultViewModelProviderFactory, extras)
} else {
ViewModelProvider.create(this)
}
return if (key != null) {
provider[key, modelClass]
} else {
provider[modelClass]
}
}

0 comments on commit c547df5

Please sign in to comment.