Colton Idle
07/26/2021, 1:28 AMScreen1
with Screen2
and Screen3
to get an idea for my duplicated code
@Composable
fun Screen1Screen(navController: NavController, viewModel: Screen1ViewModel = hiltViewModel()) {
if (viewModel.userManagerService.loggedIn) {
ActualScreen1()
} else {
navController.navigate(Screen.SignInScreen.route)
}
}
Making sure that most screens follow the above pattern seems super error prone.Adam Powell
07/26/2021, 1:38 AMnavigate
in composables. Navigate is not idempotent. In general be suspicious of any verbs invoked on any objects that outlive the current recomposition unless those methods are specifically designed to be composition-safe. (e.g. they only mutate snapshot state.) Use the *Effect
APIs to create actors in the composition that can take action if present after composition is successful.
2. You can declare your own extensions on NavGraphBuilder
that wrap the content of a destination in some navigation preamble if you like, or simply create something like a SignedInContent
composable that accepts a content: @Composable () -> Unit
Colton Idle
07/26/2021, 1:49 AMavoid calling😭. Jeez I suck at this navigation thing. lol. If only it wasn't mission critical toin composablesnavigate
Colton Idle
07/26/2021, 1:54 AMor simply create something like aOhh. That makes sense to me at least. Let me try that. That should also prevent the fact that someone could add a screen to the NavHost without thinking about whether it should behave like 99% of screens which require being SignedIn.composable that accepts aSignedInContent
content: @Composable () -> Unit
Adam Powell
07/26/2021, 1:56 AMSignedInContent
you can do something like declare a
fun NavGraphBuilder.signedInComposable(..., content: ...) {
composable(...) {
SignedInContent(..., content)
}
}
which then means when someone goes to add a screen they'll wonder why they're typing composable(...
instead of signedInComposable
like all of the other destinations in the blockAdam Powell
07/26/2021, 1:59 AMif (!signedIn) {
Column {
Text("You are logged out")
Button(onClick = { navigator.navigate("signin") }) {
Text("Sign in")
}
}
}
the sign in action is taken by the user, which mutates state, which triggers recomposition. Nothing too weird.Adam Powell
07/26/2021, 2:00 AMButton(onClick = onSignIn) {
Adam Powell
07/26/2021, 2:00 AMColton Idle
07/26/2021, 2:02 AMif (!signedIn) {
Column {
Text("You are logged out")
Button(onClick = { navigator.navigate("signin") }) {
Text("Sign in")
}
}
}
Breaking that apart
1. signedIn
is a mutableStateOf() right?
2. Shouldn't the onClick mutate signedIn
to false?Adam Powell
07/26/2021, 2:03 AMAdam Powell
07/26/2021, 2:04 AMvar clicks by remember { mutableStateOf(0) }
Text("Clicked $clicks times")
Button(onClick = { clicks++ }) {
Text("Click me")
}
Adam Powell
07/26/2021, 2:05 AMvar clicks by remember { mutableStateOf(0) }
Text("Clicked $clicks times")
// A clicker robot!
LaunchedEffect(Unit) {
while (true) {
delay(1_000)
clicks++
}
}
Colton Idle
07/26/2021, 2:09 AMAdam Powell
07/26/2021, 2:11 AMAdam Powell
07/26/2021, 2:12 AMAdam Powell
07/26/2021, 2:13 AMButton
- it's just a button that knows how to click itself rather than waiting for a user to do it. And it doesn't have to take up space in layout or draw anything either.Colton Idle
07/26/2021, 2:15 AMColton Idle
07/26/2021, 2:17 AMColton Idle
07/26/2021, 2:18 AM@Composable
fun Profile(navController: NavController) {
/*...*/
Button(onClick = { navController.navigate("friends") }) {
Text(text = "Navigate next")
}
/*...*/
}
https://developer.android.com/jetpack/compose/navigation#nav-to-composable
So I thought... maybe it's okay to just do this on my "top most" screens?Adam Powell
07/26/2021, 2:18 AMonClick
doesn't happen during compositionAdam Powell
07/26/2021, 2:20 AMColton Idle
07/26/2021, 2:21 AMsignedIn
variable which is a mutableStateOf() also falls into the category of "doesn't happen during recomposition", because I figured it wouldn't be called multiple times.Colton Idle
07/26/2021, 2:22 AMAdam Powell
07/26/2021, 2:22 AMLaunchedEffect
as a kind of actor that can be present in the compositionAdam Powell
07/26/2021, 2:22 AMsignedIn
variableColton Idle
07/26/2021, 2:27 AMthe key there is thatI think I already "knew" that. Basically in my head. I don't want to do anything really in a composable, because I just like to pretend that it can be called 100000 times without me even realizing. I like to operate under that assumption. SOOO I thought if I had adoesn't happen during compositiononClick
signedIn
mutableStateOf() then I thought even if my composable is called 10000000 times, then it would only actually execute once because signedIn
never actually changes.Colton Idle
07/26/2021, 2:28 AMAdam Powell
07/26/2021, 2:28 AMAdam Powell
07/26/2021, 2:29 AMColton Idle
07/26/2021, 2:30 AMAdam Powell
07/26/2021, 2:30 AMAdam Powell
07/26/2021, 2:30 AMAdam Powell
07/26/2021, 2:31 AMColton Idle
07/26/2021, 2:31 AMAdam Powell
07/26/2021, 2:33 AMNavHost
composable call to handle login states without a trampoline, but we'd have to work through all of the potentially complicated cases.Colton Idle
07/26/2021, 2:35 AMuserIsLoggedIn
as state, and was originally thinking that this means that I could probably just handle this on the activity level. i.e. Observe if userIsLoggedIn and then navigate based on that.Adam Powell
07/26/2021, 2:37 AMAdam Powell
07/26/2021, 2:38 AMColton Idle
07/26/2021, 2:38 AMAdam Powell
07/26/2021, 2:43 AMdimsuz
07/26/2021, 5:25 PMAdam Powell
07/26/2021, 8:50 PMAdam Powell
07/26/2021, 8:52 PMdimsuz
07/26/2021, 11:27 PMAdam Powell
07/27/2021, 12:29 AMNick
01/06/2022, 6:00 PM@Composable
fun NavGraph() {
val navController = rememberNavController()
val authenticationState =
viewModel<SignInViewModel>().authenticationState.collectAsState(initial = Unknown).value
when (authenticationState) {
is Authenticated -> {
NavHost(navController = navController, startDestination = NavRoots.ProjectRoot.route) {
projectNavigation(navController)
}
}
is Unauthenticated -> {
NavHost(navController = navController, startDestination = NavRoots.AuthRoot.route) {
authenticationNavigation(navController)
}
}
is Unknown -> Text(text = "Loading")
else -> Text(text = "else $authenticationState")
}
}
fun NavGraphBuilder.projectNavigation(navController: NavHostController) {
navigation(
startDestination = ProjectRoutes.ProjectList.route,
route = NavRoots.ProjectRoot.route
) {
composable(ProjectRoutes.ProjectList.route) {
ProjectsScreen(navController)
}
}
When I switch the navHost
via the result from the viewmodel, it’ll call navigate
under the hood, correct? So this would violate the first point that Adam made in his first message right?Adam Powell
01/06/2022, 6:25 PMNick
01/06/2022, 6:55 PMColton Idle
01/06/2022, 7:40 PMAdam Powell
01/06/2022, 9:05 PMloggedInComposable
in your NavHost
block and it's really no more cumbersomedimsuz
01/07/2022, 5:13 PM