Skip to content

Instantly share code, notes, and snippets.

@Aidanvii7
Last active August 13, 2023 16:38
Show Gist options
  • Save Aidanvii7/46f9f014ba4dc58a2ac10625b198769d to your computer and use it in GitHub Desktop.
Save Aidanvii7/46f9f014ba4dc58a2ac10625b198769d to your computer and use it in GitHub Desktop.
Compose Navigation with Parcelable arguments
@file:Suppress("UnnecessaryVariable", "PackageDirectoryMismatch")
package androidx.navigation
import android.os.Bundle
import android.os.Parcelable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
fun NavController.navigate(
route: String,
navOptions: NavOptions? = null,
navigatorExtras: Navigator.Extras? = null,
args: List<Pair<String, Parcelable>>? = null,
) {
if (args == null || args.isEmpty()) {
navigate(route, navOptions, navigatorExtras)
return
}
navigate(route, navOptions, navigatorExtras)
val addedEntry: NavBackStackEntry = backQueue.last()
val argumentBundle: Bundle = addedEntry.arguments ?: Bundle().also {
addedEntry.arguments = it
}
args.forEach { (key, arg) ->
argumentBundle.putParcelable(key, arg)
}
}
inline fun <reified T : Parcelable> NavController.navigate(
route: String,
navOptions: NavOptions? = null,
navigatorExtras: Navigator.Extras? = null,
arg: T? = null,
) {
if (arg == null) {
navigate(route, navOptions, navigatorExtras)
return
}
navigate(
route = route,
navOptions = navOptions,
navigatorExtras = navigatorExtras,
args = listOf(T::class.qualifiedName!! to arg),
)
}
inline fun <reified T : Parcelable> NavController.navigate(
route: String,
navOptions: NavOptions? = null,
navigatorExtras: Navigator.Extras? = null,
arg: Pair<String, T>? = null,
) {
if (arg == null) {
navigate(route, navOptions, navigatorExtras)
return
}
navigate(
route = route,
navOptions = navOptions,
navigatorExtras = navigatorExtras,
args = listOf(arg),
)
}
fun NavBackStackEntry.requiredArguments(): Bundle = arguments ?: throw IllegalStateException("Arguments were expected, but none were provided!")
@Composable
inline fun <reified T : Parcelable> NavBackStackEntry.rememberRequiredArgument(
key: String = T::class.qualifiedName!!,
): T = remember {
requiredArguments().getParcelable<T>(key) ?: throw IllegalStateException("Expected argument with key: $key of type: ${T::class.qualifiedName!!}")
}
@Composable
inline fun <reified T : Parcelable> NavBackStackEntry.rememberArgument(
key: String = T::class.qualifiedName!!,
): T? = remember {
arguments?.getParcelable(key)
}
val NavBackStackEntry?.routeName: String?
get() {
if (this == null) return null
val routeName: String? = destination.route?.substringBefore('/')
return routeName
}
package com.example.navigation
import android.os.Parcelable
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navigate
import androidx.navigation.rememberRequiredArgument
import androidx.navigation.routeName
import kotlinx.parcelize.Parcelize
@Parcelize
data class UserDetails(
val firstName: String,
val lastName: String,
val emailAddress: String,
val age: Int,
) : Parcelable
enum class Route {
USER_DETAILS_FORM,
USER_DETAILS_CONFIRM;
companion object {
fun from(navBackStackEntry: NavBackStackEntry?): Route? =
navBackStackEntry.routeName?.let { routeName: String ->
try {
valueOf(routeName)
} catch (e: IllegalArgumentException) {
null
}
}
}
}
@Composable
fun NavigationExample() {
val navController: NavHostController = rememberNavController()
// these 2 are optional, but useful for when you want to detect the current screen and possibly set the
// route name in the topBar composable of the Scaffold.
val currentBackStackEntry: NavBackStackEntry? by navController.currentBackStackEntryAsState()
val currentRoute: Route? = remember(currentBackStackEntry?.id) { Route.from(currentBackStackEntry) }
Scaffold(
topBar = {
// You can use the currentRoute here to add some title for each route
}
) {
NavHost(
navController = navController,
startDestination = Route.USER_DETAILS_FORM.name,
) {
composable(Route.USER_DETAILS_FORM.name) {
UserDetailsForm(
onReadyToReviewClicked = { userDetails: UserDetails ->
navController.navigate(
route = Route.USER_DETAILS_CONFIRM.name,
// passing the parcelable UserDetails as a nav arg
arg = userDetails,
)
}
)
}
composable(Route.USER_DETAILS_CONFIRM.name) { navBackStackEntry: NavBackStackEntry ->
// use the NavBackStackEntry parameter in the lambda to access the UserDetails nav arg
// since it's a required (non optional) nav arg of this screen, use the rememberRequiredArgument() extension:
val userDetails: UserDetails = navBackStackEntry.rememberRequiredArgument()
UserDetailsConfirm(
userDetails = userDetails,
onConfirmUserDetailsClicked = {
// TODO: for example, POST the details to some endpoint
}
)
}
}
}
}
@Composable
fun UserDetailsForm(
onReadyToReviewClicked: (userDetails: UserDetails) -> Unit,
modifier: Modifier = Modifier,
) {
// TODO: 1. add editable form UI fields, also some 'review' button
// TODO: 2. store form data in mutable states for each field in the form
// TODO: 3. call onReadyToReviewClicked in the 'review' button's onClick,
// with a UserDetails object (build it from the multiple mutable states that are populated by the form)
}
@Composable
fun UserDetailsConfirm(
userDetails: UserDetails,
onConfirmUserDetailsClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
// TODO: 1. add stateless UI (as in UI they can't change like in UserDetailsForm) using the passed in UserDetails, including some 'confirm' button
// TODO: 2. call onConfirmUserDetailsClicked in the 'confirm' button's onClick
}
@ahmed-shehataa
Copy link

I got these errors

  • Cannot access 'backQueue': it is private in 'NavController'
  • Val cannot be reassigned for (addedEntry.arguments = it)

@Aidanvii7
Copy link
Author

Don't use it, it doesn't work anymore. I would suggest looking for an alternative to compose navigation from Google, it's not a good solution, there's better alternatives out there.

Personally I'm using Decompose for a compose multiplatform app, but if you don't use multiplatform, you could use something else.

@ahmed-shehataa
Copy link

Okay thanks bro I found another solution

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment