Instantly share code, notes, and snippets.
Last active
August 13, 2023 16:38
-
Star
(5)
5
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save Aidanvii7/46f9f014ba4dc58a2ac10625b198769d to your computer and use it in GitHub Desktop.
Compose Navigation with Parcelable arguments
This file contains 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
@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 | |
} |
This file contains 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
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 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Okay thanks bro I found another solution