8. Abstracting navigation actions with NavIntent
Sometimes parts of your application might want to get from one place to another without knowing how to complete such action.
This is possible to achieve by using:
-
NavIntent - it is an alternative to NavAction. The difference is that NavIntent doesn't require either source or target destination to be defined.
-
NavIntentResolver - implementations of this interface, when registered with ComposeNavigation.addNavIntentResolvers, are responsible for resolving intents into NavAction or another NavIntent
In this tutorial we will add a button to the welcome screen that opens DetailScreen
with random item id.
We will start by declaring NavIntent and NavIntentResolver
:
.nav.TutorialIntent.kt
import com.adamkobus.compose.navigation.destination.NavDestination
import com.adamkobus.compose.navigation.intent.NavIntent
object TutorialIntents {
const val OPEN_RANDOM_ITEM_INTENT = "openRandomItem"
fun openRandomItem(origin: NavDestination) =
NavIntent(name = OPEN_RANDOM_ITEM_INTENT, origin = origin)
}
.nav.OpenRandomItemIntentResolver.kt
import com.adamkobus.compose.navigation.NavIntentResolver
import com.adamkobus.compose.navigation.destination.NavState
import com.adamkobus.compose.navigation.intent.NavIntent
import com.adamkobus.compose.navigation.intent.ResolveResult
import kotlin.random.Random
object OpenRandomItemIntentResolver : NavIntentResolver {
private val random = Random.Default
override suspend fun resolve(intent: NavIntent, navState: NavState): ResolveResult =
intent.origin?.takeIf {
intent.name == TutorialIntents.OPEN_RANDOM_ITEM_INTENT
}?.let { origin ->
// TutorialNavActionVerifier will be able to process this action properly
// thanks to using intent's origin as source destination for created nav action
ResolveResult.Action(origin goTo TutorialGraph.Detail arg random.nextInt())
} ?: ResolveResult.None
}
And now we will register OpenRandomItemIntentResolver
in ComposeNavigation
:
.TutorialApplication.kt
@HiltAndroidApp
class TutorialApplication : Application() {
override fun onCreate() {
super.onCreate()
ComposeNavigation
.addNavActionVerifiers(TutorialNavActionVerifier)
.addNavIntentResolvers(OpenRandomItemIntentResolver) // added
}
}
What's left is to actually use what we just created:
.ui.WelcomeScreen.kt
@Composable
private fun WelcomeScreenContent(interactions: WelcomeScreenInteractions = WelcomeScreenInteractions.STUB) {
Column {
(...)
Spacer(modifier = Modifier.height(30.dp))
Button(onClick = interactions.onOpenRandomItemClicked, modifier = Modifier.fillMaxWidth()) {
Text(text = "Open random item!")
}
}
}
class WelcomeScreenVM {
val interactions = WelcomeScreenInteractions(
(...)
onOpenRandomItemClicked = {
viewModelScope.launch {
navigationConsumer.offer(TutorialIntents.openRandomItem(TutorialGraph.Welcome))
}
}
)
}
data class WelcomeScreenInteractions(
(...)
val onOpenRandomItemClicked: () -> Unit
)
With all those things in place you should be able to open a DetailScreen
with a random item ID.
Now, this might not look too useful here. But imagine if OpenRandomItemIntentResolver
and DetailScreen
lived in their own gradle module. Thanks to NavIntent, other Gradle modules could still open the DetailScreen
without knowing anything other than the name of the intent.
This is useful when you're working on a project that can be compiled in different configurations with varying dependencies between feature modules.