-
Notifications
You must be signed in to change notification settings - Fork 63
Add recipes for returning navigation results #65
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
Open
jbw0033
wants to merge
77
commits into
android:main
Choose a base branch
from
jbw0033:results
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+4,663
−138
Open
Changes from 5 commits
Commits
Show all changes
77 commits
Select commit
Hold shift + click to select a range
917a444
Adding tests for Navigator
dturner e88d470
First draft of Navigator for nested and shared destinations
dturner 1b9681c
Address AI feedback
dturner 8a36488
Add Nav2 activity and dependencies
dturner 2104f89
Add starting point for migration
dturner 6a56eca
Step 1 of migration complete
dturner 24d2fb9
Lots of refactoring
dturner caed027
Add Step 4 of the migration
dturner 40482df
Add Step 5 of the migration
dturner 0110faa
Part way through step 6 of migration
dturner 309f912
Updating all steps
dturner 5019ac2
Add final step 7.
dturner 3e9bc63
Tidy up imports
dturner 26f754d
Merge branch 'main' into dt/2to3migration
dturner 0d9b38e
Merge branch 'main' into dt/2to3migration
dturner 17b4015
Navigator uses SavedState APIs and KotlinX Serialization to persist s…
dturner de6bdf4
Remove remaining Nav2 references from step 7
dturner 466b3a1
Handle shared routes
dturner 201f488
Switch to using Saver
dturner 1356254
Add recipes for returning navigation results
b29d10b
Fix errors on result event sample
3cc8f30
Clean up result code
jbw0033 1c25c4c
Refactor result passing with a unified ResultStore
jbw0033 6bb1fe4
Update headings in README for better structure
dturner d468a91
Minor updates to result recipe
jbw0033 ba8221f
Update to latest library versions and disable snapshot artifact repo
dturner 4141b46
Replace SnapshotStateList with NavBackStack
dturner fd7be63
Address Gemini feedback
dturner d23149d
Add link to bug
dturner 5928c4d
Merge pull request #79 from android/dt/update-versions
dturner 93f9730
Create Supporting Pane Recipe
tiwiz efee1c1
Create Supporting Pane Recipe
tiwiz 17a045d
Update dependencies to sync with Supporting Pane PR
tiwiz fb49912
Merge branch 'dependencies-update' into supporting-pane
tiwiz 28146a5
Merge pull request #81 from android/dependencies-update
ianhanniballake c324bab
Fix comments
tiwiz f178728
Merge remote-tracking branch 'origin/main' into supporting-pane
tiwiz 2f164b9
Fix comments
tiwiz f496dbc
Update README file
tiwiz d4db6e1
Fix naming
tiwiz 975d88e
Merge pull request #80 from android/supporting-pane
ianhanniballake c054009
Add koin injected ViewModel recipe
dturner 3f5fe09
Slight formatting change
dturner a8e577e
Addressing AI code review
dturner dccb089
Merge pull request #83 from android/dt/koin
ianhanniballake bce2b1b
Move Material recipes to their specific package
tiwiz 47b4df3
Update README.md
tiwiz 1f0c2ee
Update README.md
dturner 496be6a
Update README.md
dturner 8def1d6
Update README.md
dturner 804f572
Update README.md
dturner a0841fe
Merge pull request #85 from android/material
tiwiz b83fe0d
Provide a rememberResultStore Composable
jbw0033 a857897
Update to alpha10
dturner 2648ce8
Update gradle/libs.versions.toml
dturner 72108df
Add migration guide
dturner 0c9eb4b
Update guide
dturner 7510ef3
Update guide
dturner a8da9c4
Update guide
dturner 90f66ab
Update guide
dturner 52032b2
Update guide
dturner f31ea9e
Update guide
dturner f46b3fc
Update guide
dturner 5c94195
Merge branch 'main' into dt/2to3migration
dturner 26133a4
Remove navigator package
dturner ab087e7
Merge remote-tracking branch 'origin/results' into results
jbw0033 aaab38f
Merge pull request #89 from android/dt/alpha10
jbw0033 64f615e
Merge remote-tracking branch 'origin/results' into results
jbw0033 4beb9c0
Refactor to render NavDisplay on top of NavHost, rather than wrapping it
dturner 598c263
Merge branch 'main' into dt/2to3migration
dturner beeeddc
Update to use alpha10 API
dturner 00e4e2a
Merge pull request #46 from android/dt/2to3migration
dturner 78dd22b
Update links in migration guide
dturner 08b63ff
Fix link to migration package
dturner 28947aa
Fix version
dturner fbbe7e8
Merge remote-tracking branch 'origin/results' into results
jbw0033 d17bfdc
Apply suggestion from @Olabraaten
ianhanniballake File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
app/src/main/java/com/example/nav3recipes/results/event/ResultEffect.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package com.example.nav3recipes.results.event | ||
|
||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.LaunchedEffect | ||
|
||
/** | ||
* An Effect to provide a result even between different screens | ||
* | ||
* The trailing lambda provides the result from a flow of results. | ||
* | ||
* @param resultEventBus the ResultEventBus to retrieve the result from | ||
* @param resultKey the key that should be associated with this effect | ||
* @param onResult the callback to invoke when a result is received | ||
*/ | ||
@Composable | ||
inline fun <reified T> ResultEffect( | ||
resultEventBus: ResultEventBus = LocalResultEventBus.current, | ||
resultKey: String = T::class.toString(), | ||
crossinline onResult: suspend (T) -> Unit | ||
) { | ||
LaunchedEffect(resultKey, resultEventBus.channelMap[resultKey]) { | ||
resultEventBus.getResultFlow<T>()?.collect { result -> | ||
ianhanniballake marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
onResult.invoke(result as T) | ||
} | ||
} | ||
} |
127 changes: 127 additions & 0 deletions
127
app/src/main/java/com/example/nav3recipes/results/event/ResultEventActivity.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
/* | ||
* Copyright 2025 The Android Open Source Project | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.example.nav3recipes.results.event | ||
|
||
import android.os.Bundle | ||
import androidx.activity.ComponentActivity | ||
import androidx.activity.compose.setContent | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.text.input.rememberTextFieldState | ||
import androidx.compose.material3.Button | ||
import androidx.compose.material3.OutlinedTextField | ||
import androidx.compose.material3.Scaffold | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.CompositionLocalProvider | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.runtime.setValue | ||
import androidx.compose.ui.Modifier | ||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.viewmodel.compose.viewModel | ||
import androidx.navigation3.runtime.NavEntry | ||
import androidx.navigation3.runtime.NavKey | ||
import androidx.navigation3.runtime.rememberNavBackStack | ||
import androidx.navigation3.ui.NavDisplay | ||
import kotlinx.serialization.Serializable | ||
|
||
/** | ||
* This recipe demonstrates passing an event result to a previous screen. It does this by: | ||
* | ||
* - Providing a [ResultEventBus] | ||
* - Implementing a [ResultEffect] in the receiving screen | ||
* - Calling [ResultEventBus.sendResult] from the sending screen. | ||
*/ | ||
|
||
|
||
@Serializable | ||
data object Home : NavKey | ||
|
||
@Serializable | ||
class ResultPage : NavKey | ||
|
||
class ResultEventActivity : ComponentActivity() { | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
|
||
setContent { | ||
val resultBus = remember { ResultEventBus() } | ||
CompositionLocalProvider(LocalResultEventBus.provides(resultBus)) { | ||
Scaffold { paddingValues -> | ||
|
||
val backStack = rememberNavBackStack(Home) | ||
|
||
NavDisplay( | ||
backStack = backStack, | ||
modifier = Modifier.padding(paddingValues), | ||
onBack = { backStack.removeLastOrNull() }, | ||
entryProvider = { key -> | ||
when (key) { | ||
is Home -> NavEntry(key) { | ||
val viewModel = viewModel<HomeViewModel>(key = Home.toString()) | ||
ResultEffect<Name> { name -> | ||
viewModel.name = name | ||
} | ||
|
||
Column { | ||
Text("Welcome to Nav3") | ||
Button(onClick = { | ||
backStack.add(ResultPage()) | ||
}) { | ||
Text("Click to provide a name") | ||
} | ||
if (viewModel.name == null) { | ||
Text("I don't know who you are") | ||
} else { | ||
Text("Hi, ${viewModel.name}") | ||
} | ||
} | ||
} | ||
|
||
is ResultPage -> NavEntry(key) { | ||
Column { | ||
|
||
val state = rememberTextFieldState() | ||
OutlinedTextField( | ||
state = state, | ||
label = { Text("Please enter a name") } | ||
) | ||
Button(onClick = { | ||
resultBus.sendResult<Name>(result = state.text.toString()) | ||
backStack.removeLastOrNull() | ||
}) { | ||
Text("Return name") | ||
} | ||
} | ||
} | ||
else -> NavEntry(key) { Text("Unknown route") } | ||
} | ||
} | ||
) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
class HomeViewModel : ViewModel() { | ||
var name by mutableStateOf<String?>(null) | ||
} | ||
|
||
typealias Name = String |
68 changes: 68 additions & 0 deletions
68
app/src/main/java/com/example/nav3recipes/results/event/ResultEventBus.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package com.example.nav3recipes.results.event | ||
|
||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.ProvidableCompositionLocal | ||
import androidx.compose.runtime.ProvidedValue | ||
import androidx.compose.runtime.compositionLocalOf | ||
import kotlinx.coroutines.channels.BufferOverflow | ||
import kotlinx.coroutines.channels.Channel | ||
import kotlinx.coroutines.channels.Channel.Factory.BUFFERED | ||
import kotlinx.coroutines.flow.receiveAsFlow | ||
|
||
/** | ||
* Local for receiving results in a [ResultEventBus] | ||
*/ | ||
object LocalResultEventBus { | ||
private val LocalResultEventBus: ProvidableCompositionLocal<ResultEventBus?> = | ||
compositionLocalOf { null } | ||
|
||
/** | ||
* The current [ResultEventBus] | ||
*/ | ||
val current: ResultEventBus | ||
@Composable | ||
get() = LocalResultEventBus.current ?: error("No ResultStore has been provided") | ||
|
||
/** | ||
* Provides a [ResultEventBus] to the composition | ||
*/ | ||
infix fun provides( | ||
bus: ResultEventBus | ||
): ProvidedValue<ResultEventBus?> { | ||
return LocalResultEventBus.provides(bus) | ||
} | ||
} | ||
/** | ||
* An EventBus for passing results between multiple sets of screens. | ||
* | ||
* It provides a solution for event based results. | ||
*/ | ||
class ResultEventBus { | ||
/** | ||
* Map from the result key to a channel of results. | ||
*/ | ||
val channelMap: MutableMap<String, Channel<Any?>> = mutableMapOf() | ||
|
||
/** | ||
* Provides a flow for the given resultKey. | ||
*/ | ||
inline fun <reified T> getResultFlow(resultKey: String = T::class.toString()) = | ||
channelMap[resultKey]?.receiveAsFlow() | ||
|
||
/** | ||
* Sends a result into the channel associated with the given resultKey. | ||
*/ | ||
inline fun <reified T> sendResult(resultKey: String = T::class.toString(), result: T) { | ||
if (!channelMap.contains(resultKey)) { | ||
channelMap.put(resultKey, Channel(capacity = BUFFERED, onBufferOverflow = BufferOverflow.SUSPEND)) | ||
} | ||
channelMap[resultKey]?.trySend(result) | ||
} | ||
|
||
/** | ||
* Removes all results associated with the given key from the store. | ||
*/ | ||
inline fun <reified T> removeResult(resultKey: String = T::class.toString()) { | ||
channelMap.remove(resultKey) | ||
} | ||
} |
115 changes: 115 additions & 0 deletions
115
app/src/main/java/com/example/nav3recipes/results/state/ResultStateActivity.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
/* | ||
* Copyright 2025 The Android Open Source Project | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.example.nav3recipes.results.state | ||
|
||
import android.os.Bundle | ||
import androidx.activity.ComponentActivity | ||
import androidx.activity.compose.setContent | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.text.input.rememberTextFieldState | ||
import androidx.compose.material3.Button | ||
import androidx.compose.material3.OutlinedTextField | ||
import androidx.compose.material3.Scaffold | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.CompositionLocalProvider | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.ui.Modifier | ||
import androidx.navigation3.runtime.NavEntry | ||
import androidx.navigation3.runtime.NavKey | ||
import androidx.navigation3.runtime.rememberNavBackStack | ||
import androidx.navigation3.ui.NavDisplay | ||
import kotlinx.serialization.Serializable | ||
|
||
/** | ||
* This recipe demonstrates passing an state result to a previous screen. It does this by: | ||
* | ||
* - Providing a [ResultStore] | ||
* - Calling [ResultStore.getResultState] in the receiving screen | ||
* - Calling [ResultStore.setResult] from the sending screen. | ||
*/ | ||
|
||
@Serializable | ||
data object Home : NavKey | ||
|
||
@Serializable | ||
class ResultPage : NavKey | ||
|
||
class ResultStateActivity : ComponentActivity() { | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
|
||
setContent { | ||
val resultStore = remember { ResultStore() } | ||
CompositionLocalProvider(LocalResultStore.provides(resultStore)) { | ||
Scaffold { paddingValues -> | ||
val backStack = rememberNavBackStack(Home) | ||
|
||
NavDisplay( | ||
backStack = backStack, | ||
modifier = Modifier.padding(paddingValues), | ||
onBack = { backStack.removeLastOrNull() }, | ||
entryProvider = { key -> | ||
when (key) { | ||
is Home -> NavEntry(key) { | ||
val name = resultStore.getResultState<Name?>() | ||
|
||
Column { | ||
Text("Welcome to Nav3") | ||
Button(onClick = { | ||
backStack.add(ResultPage()) | ||
}) { | ||
Text("Click to provide a name") | ||
} | ||
if (name == null) { | ||
Text("I don't know who you are") | ||
} else { | ||
Text("Hi, $name") | ||
} | ||
} | ||
|
||
|
||
} | ||
is ResultPage -> NavEntry(key) { | ||
Column { | ||
|
||
val state = rememberTextFieldState() | ||
OutlinedTextField( | ||
state = state, | ||
label = { Text("Please enter a name") } | ||
) | ||
|
||
Button(onClick = { | ||
resultStore.setResult<Name>(result = state.text.toString()) | ||
backStack.removeLastOrNull() | ||
}) { | ||
Text("Return name") | ||
} | ||
} | ||
} | ||
else -> NavEntry(key) { Text("Unknown route") } | ||
} | ||
} | ||
) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
typealias Name = String |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.