-
-
Notifications
You must be signed in to change notification settings - Fork 109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds support for result passing between screens #128
base: main
Are you sure you want to change the base?
Conversation
@Kashif-E HI, we will have a look on this soon (testing and trying on our samples), don't know if it will be for 1.0 that will be soon released. Btw thanks for the contribution! I will let you know soon! |
...ator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/bottomSheet/BottomSheetNavigator.kt
Outdated
Show resolved
Hide resolved
97053dc
to
2792058
Compare
# Conflicts: # voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/Navigator.kt
Hey guys, any updates on this? |
@DevSrSouza any update ? |
@DevSrSouza @adrielcafe this is something we really need can you please help here? |
@Kashif-E Have you given up on this? :( |
@jakoss i hate to admit this but yes, but, i am thinking about making my fork a separate library. |
More fragmentation for navigation libraries for compose, but i understand the reason. This thread is too ignored at this point |
@Kashif-E Can we get back here? Forgive us for inactivity, this feature is very important and we want it on Voyager Please let me know if you can continue here, I will take care of reviewing it and ensuring it is merged, if not possible I will continue it myself. Thank you in advance for your time and contribution!! 🙏🏽 |
I don't feel like this implementation is ideal for Voyager, I would prefer something like the one displayed here would be better. I feel it clutters the navigator when it could just be a module added on-top of it |
1 issue I find with this is that it uses String keys, I find that with Kotlin I would prefer to have a TypeSafe key to Value object that allows managing types inherently. Something like this interface TypedKey<T>
object DataId : TypedKey<Long>
private val typedKeyMap: Map<TypedKey<*>, Any>
fun <T> putInTypedKeyMap(typedKey: TypedKey<T>, value: T): T {
typedKeyMap[typedKey] = value
}
fun <T> getFromTypedKeyMap(typedKey: TypedKey<T>): T {
return typedKeyMap[typedKey] as T
}
fun inputMyData() {
putInTypedKeyMap(DataId, 100L)
}
fun getMyData() {
val myDataId: Long = getFromTypedKeyMap(DataId)
} |
@Syer10 Great! What we want here is the "possibility of passing results between screens" the way, api, imp in which this will be done has not yet been defined. Also agree that such a feature should be something "more" and not tied to the navigator. What you said will be considered, thanks! |
@Syer10 what you said is perfectly right. However, there are a few things that should be considered here
|
@@ -114,33 +116,41 @@ public class Navigator @InternalVoyagerApi constructor( | |||
public val parent: Navigator? = null | |||
) : Stack<Screen> by screens.toMutableStateStack(minSize = 1) { | |||
|
|||
public val level: Int = | |||
parent?.level?.inc() ?: 0 | |||
public var isNavigating: Boolean by mutableStateOf(false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apparently, this property is never used
@OptIn(ExperimentalVoyagerApi::class, InternalVoyagerApi::class) | ||
@Composable | ||
public fun saveableState( | ||
key: String, | ||
screen: Screen = lastItem, | ||
content: @Composable () -> Unit | ||
key: String, screen: Screen = lastItem, content: @Composable () -> Unit | ||
) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing did change here right? can you revert this?
if (!(screen.key.contains("onboarding", ignoreCase = true) && stateKey.contains( | ||
"onboarding", ignoreCase = true | ||
)) | ||
) { | ||
stateKeys += stateKey | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this "onboarding" validation here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was our own implementation we can ignore this
MultipleProvideBeforeScreenContent( | ||
screenLifecycleContentProviders = composed, | ||
MultipleProvideBeforeScreenContent(screenLifecycleContentProviders = composed, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be reverted
stateKeys | ||
.asSequence() | ||
.filter { it.startsWith(screen.key) } | ||
.forEach { key -> | ||
stateHolder.removeState(key) | ||
stateKeys -= key | ||
} | ||
stateKeys.asSequence().filter { it.startsWith(screen.key) }.forEach { key -> | ||
stateHolder.removeState(key) | ||
stateKeys -= key | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can reverted as well
I have baked your API without requiring to change any internal voyager APIs, could you try it out? If it works for you, we could provide this a result API examples. val Navigator.navigationResult: VoyagerResultExtension
@Composable get() = remember {
NavigatorLifecycleStore.register(this) {
VoyagerResultExtension(this)
}
}
// OR
@Composable
public fun rememberNavigationResultExtension(): VoyagerResultExtension {
val navigator = LocalNavigator.currentOrThrow
return remember {
NavigatorLifecycleStore.get(navigator) {
VoyagerResultExtension(navigator)
}
}
}
class VoyagerResultExtension(
private val navigator: Navigator
) : NavigatorDisposable {
private val results = mutableStateMapOf<String, Any?>()
override fun onDispose(navigator: Navigator) {
// not used
}
public fun setResult(screenKey: String, result: Any?) {
results[screenKey] = result
}
public fun popWithResult(result: Any? = null) {
val currentScreen = navigator.lastItem
results[currentScreen.key] = result
navigator.pop()
}
public fun clearResults() {
results.clear()
}
public fun popUntilWithResult(predicate: (Screen) -> Boolean, result: Any? = null) {
val currentScreen = navigator.lastItem
results[currentScreen.key] = result
navigator.popUntil(predicate)
}
@Composable
public fun <T> getResult(screenKey: String): State<T?> {
val result = results[screenKey] as? T
val resultState = remember(screenKey, result) {
derivedStateOf {
results.remove(screenKey)
result
}
}
return resultState
}
} |
+1 for @DevSrSouza's implementation |
this looks great @DevSrSouza. ill test this in our app and will let you know how this works |
This could get into the documentation IMHO @DevSrSouza |
Is it possible to use this snippet with a BottomSheetNavigator? @DevSrSouza |
@DevSrSouza it works, 🎆🎆 @osrl both mine and what @DevSrSouza proposed can be used with bottomsheet navigator |
|
@osrl you can use these extensions, remove the actual keyword
|
Hello, is it going to be supported by the library, or you prefer to keep it as extension function and everybody just copy paste it? |
@DevSrSouza This works, it's great, but it seems a little cumbersome to use, maybe I'm using it the wrong way? However, due to life cycle issues, the first page can only get the current value by directly using var lastItemKey: String? by rememberSaveable {
mutableStateOf(null)
}
Box(modifier = Modifier.clickable {
navigator.push(SecondPageScreen())
lastItemKey = navigator.lastItem.key
})
if (lastItemKey != null) {
val result by navigator.navigationResult
.getResult<String>(lastItemKey!!)
} |
@0xZhangKe it does not matter what you use as key for example you use "papi chulo" as key when passing back result and use it on first screen |
1 similar comment
@0xZhangKe it does not matter what you use as key for example you use "papi chulo" as key when passing back result and use it on first screen |
@Kashif-E This means that the first page must clearly know the Key of the second page. If the two Screens are in different modules and cannot reference each other, then this will become a bit troublesome. |
@0xZhangKe try this
|
I think that there is a confusion here. Voyager is Screen based navigation and if you needs the result it should be handled by the previous screen and not returned to the navigation stack action call. interface PopResult<in T> {
fun onResult(result: T)
}
class MyScreen : Screen, PopResult<T> {
fun onResult(result: T) {
// handle the popped result
}
}
// Inside screen Content() that will be popped
val navigator = LocalNavigator.currentOrThrow
navigator.pop() // It is a sync call and will not trigger recomposition immediatelly
val nextScreen = navigator.lastItemOrNull as? PopResult<T>
nextScreen?.onResult(...) |
meanwhile there is no official support, I'm currently using this if anyone needs, it enforces some type safety by sending "request" to the next screen. It's almost the same as DevSrSouza proposal, but nav library agnostic. Ofc, if it's not implemented correctly an value can be held in memory until the app is closed by the user and even potentially exceed parcelable size limit // Root
ProvideNavResultStateHolder() {
Navigator(Screen1())
}
// Screen1
val contract = rememberNavResultContract<Int>(key)
val response = contract.response.value
val isCurrentScreen = navigator.isCurrentScreen(key)
LaunchedEffect(response, isCurrentScreen) {
if(!isCurrentScreen) return@LaunchedEffect
if(response !is NavResult.Value) return@LaunchedEffect
// Do something
contract.consume()
}
Button(
onClick = {
navigator.push(Screen2(contract.request))
}
) {
Text("Push")
}
// Screen2
val contract = rememberNavResultContract<Int>(request)
Button(
onClick = {
contract.send(0)
navigator.pop()
}
) {
Text("Pop")
}
|
We all are still waiting for this PR. |
Any updates? |
We all are still waiting |
Where is this |
It was renamed to |
thanks, did look into it but not quite sure |
Closes #110
Adds support to pass results between screen for
Navigator
andBottomSheetNavigator
.using Navigator.
Passing the result
Getting the result
using BottomSheetNavigator:
Passing the result
Getting the result: