Skip to content

Commit

Permalink
fix: viewmodel lifecycle
Browse files Browse the repository at this point in the history
  • Loading branch information
adrielcafe committed Aug 27, 2021
1 parent 51803a0 commit 2fd7081
Show file tree
Hide file tree
Showing 20 changed files with 196 additions and 156 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
<a href="https://voyager.adriel.cafe">Voyager</a>: Compose on Warp Speed
</h1>

A lightweight and pragmatic navigation library built for, and seamlessly integrated with, [Jetpack Compose](https://developer.android.com/jetpack/compose).
Voyager is a lightweight and complete navigation library built for, and seamlessly integrated with, [Jetpack Compose](https://developer.android.com/jetpack/compose).

Turn on the Warp Drive and enjoy the trek 🖖
Create scalable Single-Activity apps powered by a [pragmatic API](https://voyager.adriel.cafe/navigation/fundamentals).

```kotlin
class HomeScreen : Screen {
Expand All @@ -35,19 +35,19 @@ class SingleActivity : ComponentActivity() {
}
```

Turn on the Warp Drive and enjoy the trek 🖖

### Documentation
See the [project website](https://voyager.adriel.cafe) for documentation and APIs.

### Features
- Create scalable Single-Activity apps powered by a [pragmatic API](https://voyager.adriel.cafe/navigation/fundamentals)
- [BottomSheet navigation](https://voyager.adriel.cafe/navigation/bottomsheet-navigation)
- [Tab navigation](https://voyager.adriel.cafe/navigation/tab-navigation) like [Youtube app](https://play.google.com/store/apps/details?id=com.google.android.youtube)
- [Nested navigation](https://voyager.adriel.cafe/navigation/nested-navigation) if you need to manage multiple stacks
- [State restoration](https://voyager.adriel.cafe/state-restoration) after Activity recreation
- Type-safe [multi-module navigation](https://voyager.adriel.cafe/navigation/multi-module-navigation)
- State-aware [Stack API](https://voyager.adriel.cafe/stack-api)
- Built-in [transitions](https://voyager.adriel.cafe/transitions)
- Pluggable [hooks](https://voyager.adriel.cafe/hooks)
- [Lifecycle](https://voyager.adriel.cafe/lifecycle) callbacks
- [Back press](https://voyager.adriel.cafe/back-press) handling
- [Deep linking](https://voyager.adriel.cafe/deep-links) support
Expand All @@ -60,6 +60,6 @@ See the [project website](https://voyager.adriel.cafe) for documentation and API
|------------|----------|-------------|
| ![navigation-stack](https://user-images.githubusercontent.com/2512298/126323192-9b6349fe-7b96-4acf-b62e-c75165d909e1.gif) | ![navigation-androidx](https://user-images.githubusercontent.com/2512298/130377801-c350b4f5-bcca-4d28-9403-0d9d4c1e99f7.gif) | ![navigation-basic](https://user-images.githubusercontent.com/2512298/126323165-47760eec-2ba2-48ee-8e3a-841d50098d33.gif) |

| [Tab nav.](https://github.com/adrielcafe/voyager/tree/main/sample/src/main/java/cafe/adriel/voyager/sample/tabNavigation) | [Multi-module nav.](https://github.com/adrielcafe/voyager/tree/main/sample-multi-module) | [Nested nav.](https://github.com/adrielcafe/voyager/tree/main/sample/src/main/java/cafe/adriel/voyager/sample/nestedNavigation) |
|------------|----------|-------------|
| ![navigation-tab](https://user-images.githubusercontent.com/2512298/126323588-2f970953-0adb-47f8-b2fb-91c5854656bd.gif) | ![navigation-multi-module](https://user-images.githubusercontent.com/2512298/130662717-c15caf88-350e-42a0-837c-3453805b68f2.gif) | ![navigation-nested](https://user-images.githubusercontent.com/2512298/126323027-a2633aef-9402-4df8-9384-45935d7986cf.gif) |
| [BottomSheet nav.](https://github.com/adrielcafe/voyager/tree/main/sample/src/main/java/cafe/adriel/voyager/sample/bottomSheetNavigation) | [Tab nav.](https://github.com/adrielcafe/voyager/tree/main/sample/src/main/java/cafe/adriel/voyager/sample/tabNavigation) | [Multi-module nav.](https://github.com/adrielcafe/voyager/tree/main/sample-multi-module) | [Nested nav.](https://github.com/adrielcafe/voyager/tree/main/sample/src/main/java/cafe/adriel/voyager/sample/nestedNavigation) |
|------------|------------|----------|-------------|
| ![navigation-bottom-sheet](https://user-images.githubusercontent.com/2512298/131191122-18025192-ce4d-4659-9afa-aacfdb488796.gif) | ![navigation-tab](https://user-images.githubusercontent.com/2512298/126323588-2f970953-0adb-47f8-b2fb-91c5854656bd.gif) | ![navigation-multi-module](https://user-images.githubusercontent.com/2512298/130662717-c15caf88-350e-42a0-837c-3453805b68f2.gif) | ![navigation-nested](https://user-images.githubusercontent.com/2512298/126323027-a2633aef-9402-4df8-9384-45935d7986cf.gif) |
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ kotlin.code.style=official

# Maven
GROUP=cafe.adriel.voyager
VERSION_NAME=1.0.0-beta08
VERSION_NAME=1.0.0-beta09

POM_DESCRIPTION=A pragmatic navigation library for Jetpack Compose
POM_INCEPTION_YEAR=2021
Expand Down
4 changes: 4 additions & 0 deletions sample/src/main/java/cafe/adriel/voyager/sample/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cafe.adriel.voyager.sample

import android.app.Application
import cafe.adriel.voyager.sample.androidNavigation.DetailsViewModel
import cafe.adriel.voyager.sample.androidNavigation.ListViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.core.context.startKoin
import org.koin.dsl.module
Expand All @@ -13,6 +14,9 @@ class App : Application() {
startKoin {
modules(
module {
viewModel {
ListViewModel(handle = get())
}
viewModel { parameters ->
DetailsViewModel(index = parameters.get())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package cafe.adriel.voyager.sample.androidNavigation

import androidx.compose.foundation.clickable
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ListItem
import androidx.compose.material.Text
Expand All @@ -10,18 +12,20 @@ import androidx.compose.ui.Modifier
import cafe.adriel.voyager.androidx.AndroidScreen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import org.koin.androidx.compose.getStateViewModel

class ListScreen : AndroidScreen() {

@OptIn(ExperimentalMaterialApi::class)
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val viewModel = getStateViewModel<ListViewModel>()

LazyColumn {
items(100) { index ->
itemsIndexed(viewModel.items) { index, item ->
ListItem(
text = { Text(text = "Item $index") },
text = { Text(text = item) },
modifier = Modifier.clickable { navigator.push(DetailsScreen(index)) }
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package cafe.adriel.voyager.sample.androidNavigation

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import java.util.UUID

class ListViewModel(private val handle: SavedStateHandle) : ViewModel() {

init {
handle["items"] = (0..100).map { "Item #$it | ${UUID.randomUUID().toString().substringBefore('-')}" }
}

val items: List<String>
get() = handle["items"] ?: error("Items not found")
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.LifecycleEffect
import cafe.adriel.voyager.core.lifecycle.LifecycleEffect
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.core.screen.uniqueScreenKey
import cafe.adriel.voyager.navigator.LocalNavigator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.LifecycleEffect
import cafe.adriel.voyager.core.lifecycle.LifecycleEffect
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
import cafe.adriel.voyager.navigator.tab.Tab
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package cafe.adriel.voyager.androidx

import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleOwner
import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleProvider
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.core.screen.ScreenKey
import cafe.adriel.voyager.core.screen.uniqueScreenKey

public abstract class AndroidScreen : Screen, ScreenLifecycleOwner by ScreenLifecycleHolder() {
public abstract class AndroidScreen : Screen, ScreenLifecycleProvider {

override val key: String = uniqueScreenKey
override val key: ScreenKey = uniqueScreenKey

override fun getLifecycleOwner(): ScreenLifecycleOwner = ScreenLifecycleHolder.get(key)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package cafe.adriel.voyager.androidx

import android.app.Activity
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryController
import androidx.savedstate.SavedStateRegistryOwner
import cafe.adriel.voyager.core.lifecycle.ScreenHooks
import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleOwner
import cafe.adriel.voyager.core.screen.ScreenKey
import java.util.concurrent.ConcurrentHashMap

public class ScreenLifecycleHolder private constructor(
private val key: ScreenKey
) : ScreenLifecycleOwner,
LifecycleOwner,
ViewModelStoreOwner,
SavedStateRegistryOwner {

private val registry = LifecycleRegistry(this)

private val store = ViewModelStore()

private val controller = SavedStateRegistryController.create(this)

private val Context.canDispose: Boolean
get() = (this as? Activity)?.isChangingConfigurations?.not() ?: true

init {
controller.performRestore(null)
}

@Composable
override fun getHooks(): ScreenHooks {
val context = LocalContext.current

return ScreenHooks(
providers = listOf(
LocalViewModelStoreOwner provides this,
LocalSavedStateRegistryOwner provides this,
),
disposer = {
if (context.canDispose) {
viewModelStore.clear()
remove(key)
}
}
)
}

override fun getLifecycle(): Lifecycle = registry

override fun getViewModelStore(): ViewModelStore = store

override fun getSavedStateRegistry(): SavedStateRegistry = controller.savedStateRegistry

internal companion object {

private val holders = ConcurrentHashMap<ScreenKey, ScreenLifecycleHolder>()

internal fun get(key: ScreenKey) =
holders.getOrPut(key) { ScreenLifecycleHolder(key) }

private fun remove(key: ScreenKey) {
holders -= key
}
}
}

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cafe.adriel.voyager.core.lifecycle

import androidx.compose.runtime.ProvidedValue

public data class ScreenHooks(
val providers: List<ProvidedValue<*>> = emptyList(),
val disposer: () -> Unit = {}
) {

internal companion object {
val Empty = ScreenHooks()
}
}
Loading

0 comments on commit 2fd7081

Please sign in to comment.