Skip to content

Commit

Permalink
Merge pull request #9 from hoc081098/sample :shipit:
Browse files Browse the repository at this point in the history
Sample
  • Loading branch information
hoc081098 authored Jan 6, 2024
2 parents acd9a29 + 398ba09 commit 7f2c08e
Show file tree
Hide file tree
Showing 76 changed files with 909 additions and 859 deletions.
2 changes: 1 addition & 1 deletion .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,23 @@ sample-android-target = "34"
sample-android-compile = "34"

jetbrains-compose = "1.5.11"
jetbrains-compose-compiler = "1.5.7.1"

touchlab-stately = "1.2.5"
napier = "2.6.1"
flowExt = "0.7.4"
koin = "3.5.0"
koin-compose = "1.1.0"
koin-androidx-compose = "3.5.0"
coil = "2.5.0"
compose-rules-detekt = "0.3.8"

androidx-lifecycle = "2.6.2"
androidx-annotation = "1.7.1"
androidx-activity = "1.8.2"
androidx-appcompat = "1.6.1"
androidx-compose-compiler = "1.5.6"
androidx-core-ktx = "1.12.0"
androidx-navigation = "2.7.6"
android-gradle = "8.2.0"

Expand All @@ -57,6 +61,7 @@ ktlint = "0.50.0"
[libraries]
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "coroutines" }
coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
coroutines-jdk8 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8", version.ref = "coroutines" }
junit = { module = "junit:junit", version.ref = "junit" }
Expand All @@ -68,6 +73,7 @@ touchlab-stately-concurrency = { module = "co.touchlab:stately-concurrency", ver
napier = { module = "io.github.aakira:napier", version.ref = "napier" }
flowExt = { module = "io.github.hoc081098:FlowExt", version.ref = "flowExt" }
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin-compose" }
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin-androidx-compose" }
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
Expand All @@ -79,6 +85,7 @@ androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-ru
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version = "androidx-lifecycle" }
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
androidx-compose-bom = { module = "androidx.compose:compose-bom", version = "2023.10.01" }
androidx-compose-ui-ui = { module = "androidx.compose.ui:ui" }
androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
Expand All @@ -87,6 +94,7 @@ androidx-compose-foundation = { module = "androidx.compose.foundation:foundation
androidx-compose-material = { module = "androidx.compose.material:material" }
androidx-compose-material3 = { module = "androidx.compose.material3:material3" }
androidx-compose-runtime = { module = "androidx.compose.runtime:runtime" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core-ktx" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }

kmp-viewmodel-core = { module = "io.github.hoc081098:kmp-viewmodel", version.ref = "kmp-viewmodel" }
Expand Down
2 changes: 1 addition & 1 deletion khonshu-navigation-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ plugins {
}

compose {
kotlinCompilerPlugin.set("1.5.7")
kotlinCompilerPlugin.set(libs.versions.jetbrains.compose.compiler)
}

kotlin {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.hoc081098.solivagant.navigation.internal

import com.hoc081098.kmp.viewmodel.SavedStateHandle
import com.hoc081098.kmp.viewmodel.SavedStateHandleFactory
import com.hoc081098.solivagant.navigation.BaseRoute
import com.hoc081098.solivagant.navigation.Navigator
import com.hoc081098.solivagant.navigation.Serializable
Expand All @@ -10,6 +11,7 @@ import kotlin.reflect.KClass
public interface NavigationExecutor : Navigator {
public fun <T : BaseRoute> routeFor(destinationId: DestinationId<T>): T
public fun <T : BaseRoute> savedStateHandleFor(destinationId: DestinationId<T>): SavedStateHandle
public fun <T : BaseRoute> savedStateHandleFactoryFor(destinationId: DestinationId<T>): SavedStateHandleFactory
public fun <T : BaseRoute> storeFor(destinationId: DestinationId<T>): Store
public fun <T : BaseRoute> extra(destinationId: DestinationId<T>): Serializable

Expand Down
2 changes: 1 addition & 1 deletion navigation-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ plugins {
}

compose {
kotlinCompilerPlugin.set("1.5.7")
kotlinCompilerPlugin.set(libs.versions.jetbrains.compose.compiler)
}

kotlin {
Expand Down
2 changes: 1 addition & 1 deletion navigation/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ plugins {
}

compose {
kotlinCompilerPlugin.set("1.5.7")
kotlinCompilerPlugin.set(libs.versions.jetbrains.compose.compiler)
}

kotlin {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.hoc081098.solivagant.navigation.internal

import com.hoc081098.kmp.viewmodel.InternalKmpViewModelApi
import com.hoc081098.kmp.viewmodel.ViewModelStore

@OptIn(InternalKmpViewModelApi::class)
internal actual fun createViewModelStore(): ViewModelStore =
ViewModelStore(androidx.lifecycle.ViewModelStore())
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,25 @@ internal actual fun SavedStateHandle.getAsMap(key: String): Map<String, Any?>? =
?.let { bundle ->
bundle
.keySet()
.associateWith {
@Suppress("DEPRECATION")
bundle.get(it)
}
.associateWith { bundle.safeGet(it) }
}

private fun Bundle.toMap(): Map<String, Any?> = keySet().associateWith { safeGet(it) }

private fun Bundle.safeGet(key: String): Any? {
@Suppress("DEPRECATION")
return when (val v = get(key)) {
is Bundle -> return v.toMap()
is ArrayList<*> -> {
when (v[0]) {
is Bundle -> v.mapTo(ArrayList(v.size)) { (it as Bundle).toMap() }
else -> v
}
}
else -> v
}
}

@SuppressLint("RestrictedApi")
internal actual fun createSavedStateHandleAndSetSavedStateProvider(
id: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,22 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.SaveableStateHolder
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.ui.Modifier
import com.hoc081098.kmp.viewmodel.Closeable
import com.hoc081098.kmp.viewmodel.compose.SavedStateHandleFactoryProvider
import com.hoc081098.kmp.viewmodel.compose.ViewModelStoreOwnerProvider
import com.hoc081098.solivagant.navigation.internal.MultiStackNavigationExecutor
import com.hoc081098.solivagant.navigation.internal.OnBackPressedCallback
import com.hoc081098.solivagant.navigation.internal.StackEntry
import com.hoc081098.solivagant.navigation.internal.StackEntryViewModelStoreOwner
import com.hoc081098.solivagant.navigation.internal.WeakReference
import com.hoc081098.solivagant.navigation.internal.currentBackPressedDispatcher
import com.hoc081098.solivagant.navigation.internal.rememberNavigationExecutor
Expand Down Expand Up @@ -50,7 +57,11 @@ public fun NavHost(

Box(modifier = modifier) {
executor.visibleEntries.value.forEach { entry ->
Show(entry, executor, saveableStateHolder)
Show(
entry = entry,
executor = executor,
saveableStateHolder = saveableStateHolder,
)
}
}
}
Expand All @@ -67,22 +78,50 @@ private fun <T : BaseRoute> Show(
// it is available when the destination is cleared. Which, because of animations,
// only happens after this leaves composition. Which means we can't rely on
// DisposableEffect to clean up this reference (as it'll be cleaned up too early)
remember(entry, executor, saveableStateHolder) {
executor.storeFor(entry.id).getOrCreate(SaveableCloseable::class) {
SaveableCloseable(entry.id.value, WeakReference(saveableStateHolder))
}
val saveableCloseable = remember(entry, executor, saveableStateHolder) {
executor
.storeFor(entry.id)
.getOrCreate(SaveableCloseable::class) {
SaveableCloseable(
entry.id.value,
WeakReference(saveableStateHolder),
)
}
}

val viewModelStoreOwner = saveableCloseable
.viewModelStoreOwnerState
.value // <-- This will cause the recomposition when the value is cleared.
?: return

saveableStateHolder.SaveableStateProvider(entry.id.value) {
entry.destination.content(entry.route)
ViewModelStoreOwnerProvider(
viewModelStoreOwner = viewModelStoreOwner,
) {
SavedStateHandleFactoryProvider(
savedStateHandleFactory = executor.savedStateHandleFactoryFor(entry.destinationId),
) {
entry.destination.content(entry.route)
}
}
}
}

internal class SaveableCloseable(
private val id: String,
private val saveableStateHolderRef: WeakReference<SaveableStateHolder>,
) : Closeable {
private val _viewModelStoreOwnerState: MutableState<StackEntryViewModelStoreOwner?> =
mutableStateOf(StackEntryViewModelStoreOwner())

inline val viewModelStoreOwnerState: State<StackEntryViewModelStoreOwner?> get() = _viewModelStoreOwnerState

override fun close() {
Snapshot.withMutableSnapshot {
_viewModelStoreOwnerState.value?.clearIfInitialized()
_viewModelStoreOwnerState.value = null
}

saveableStateHolderRef.get()?.removeState(id)
saveableStateHolderRef.clear()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.hoc081098.solivagant.navigation.internal

import androidx.compose.runtime.State
import com.hoc081098.kmp.viewmodel.SavedStateHandle
import com.hoc081098.kmp.viewmodel.SavedStateHandleFactory
import com.hoc081098.solivagant.navigation.BaseRoute
import com.hoc081098.solivagant.navigation.NavRoot
import com.hoc081098.solivagant.navigation.NavRoute
Expand Down Expand Up @@ -64,7 +65,12 @@ internal class MultiStackNavigationExecutor(

override fun <T : BaseRoute> savedStateHandleFor(destinationId: DestinationId<T>): SavedStateHandle {
val entry = entryFor(destinationId)
return viewModel.provideSavedStateHandle(entry.id)
return viewModel.provideSavedStateHandle(entry.id, entry.route)
}

override fun <T : BaseRoute> savedStateHandleFactoryFor(destinationId: DestinationId<T>): SavedStateHandleFactory {
val entry = entryFor(destinationId)
return viewModel.provideSavedStateHandleFactory(entry.id, entry.route)
}

override fun <T : BaseRoute> storeFor(destinationId: DestinationId<T>): NavigationExecutor.Store {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.hoc081098.solivagant.navigation.internal

import com.hoc081098.kmp.viewmodel.ViewModelStore
import com.hoc081098.kmp.viewmodel.ViewModelStoreOwner
import kotlin.LazyThreadSafetyMode.NONE

internal expect fun createViewModelStore(): ViewModelStore

internal class StackEntryViewModelStoreOwner : ViewModelStoreOwner {
private val viewModelStoreLazy = lazy(NONE) { createViewModelStore() }

fun clearIfInitialized() {
if (viewModelStoreLazy.isInitialized()) {
viewModelStoreLazy.value.clear()
}
}

override val viewModelStore: ViewModelStore by viewModelStoreLazy
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.hoc081098.solivagant.navigation.internal

import com.hoc081098.kmp.viewmodel.SavedStateHandle
import com.hoc081098.kmp.viewmodel.SavedStateHandleFactory
import com.hoc081098.kmp.viewmodel.ViewModel
import com.hoc081098.solivagant.navigation.BaseRoute
import com.hoc081098.solivagant.navigation.EXTRA_ROUTE
import com.hoc081098.solivagant.navigation.NavRoot
import com.hoc081098.solivagant.navigation.internal.MultiStackNavigationExecutor.Companion.SAVED_STATE_STACK

Expand All @@ -10,24 +13,33 @@ internal class StoreViewModel(
) : ViewModel() {
private val stores = mutableMapOf<StackEntry.Id, NavigationExecutorStore>()
private val savedStateHandles = mutableMapOf<StackEntry.Id, SavedStateHandle>()
private val savedStateHandleFactories = mutableMapOf<StackEntry.Id, SavedStateHandleFactory>()

internal val savedNavRoot: NavRoot? get() = globalSavedStateHandle[SAVED_START_ROOT_KEY]

fun provideStore(id: StackEntry.Id): NavigationExecutor.Store {
internal fun provideStore(id: StackEntry.Id): NavigationExecutor.Store {
return stores.getOrPut(id) { NavigationExecutorStore() }
}

fun provideSavedStateHandle(id: StackEntry.Id): SavedStateHandle {
internal fun provideSavedStateHandle(id: StackEntry.Id, route: BaseRoute): SavedStateHandle {
return savedStateHandles.getOrPut(id) {
createSavedStateHandleAndSetSavedStateProvider(id.value, globalSavedStateHandle)
.apply { this[EXTRA_ROUTE] = route }
}
}

fun removeEntry(id: StackEntry.Id) {
internal fun provideSavedStateHandleFactory(id: StackEntry.Id, route: BaseRoute): SavedStateHandleFactory {
return savedStateHandleFactories.getOrPut(id) {
SavedStateHandleFactory { provideSavedStateHandle(id, route) }
}
}

internal fun removeEntry(id: StackEntry.Id) {
val store = stores.remove(id)
store?.close()

savedStateHandles.remove(id)
savedStateHandleFactories.remove(id)
globalSavedStateHandle.removeSavedStateProvider(id.value)
globalSavedStateHandle.remove<Any>(id.value)
}
Expand All @@ -44,6 +56,7 @@ internal class StoreViewModel(
globalSavedStateHandle.remove<Any>(key.value)
}
savedStateHandles.clear()
savedStateHandleFactories.clear()
}

internal fun setInputStartRoot(root: NavRoot) {
Expand Down Expand Up @@ -73,7 +86,7 @@ internal class StoreViewModel(
globalSavedStateHandle[SAVED_START_ROOT_KEY] = root
}

fun getSavedStackState(): Map<String, Any?>? =
internal fun getSavedStackState(): Map<String, Any?>? =
globalSavedStateHandle.getAsMap(SAVED_STATE_STACK)

private companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.hoc081098.solivagant.navigation.internal

import com.hoc081098.kmp.viewmodel.ViewModelStore

internal actual fun createViewModelStore(): ViewModelStore = ViewModelStore()
Loading

0 comments on commit 7f2c08e

Please sign in to comment.