Hello, I’m having a weird issue which I can’t real...
# compose
v
Hello, I’m having a weird issue which I can’t really understand the problem. To simplify things, I have two Composables, SplashScreen & HomeScreen. I need to make a network request on the SplashScreen and then navigate to HomeScreen when the endpoint succeeds. Code in thread 👇
1
c
@Viktor Petrovski can you edit your post to put the code in this thread please? https://kotlinlang.slack.com/archives/CJLTWPH7S/p1616265877303000
v
Yes sure, sorry @Colton Idle
NavigationCode:
Copy code
@Composable
fun Navigation() {
    val navController = rememberNavController()
    NavHost(navController = navController, startDestination = Screens.SplashScreen.route) {
        composable(Screens.SplashScreen.route) {
            val authViewModel = hiltViewModel<AuthViewModel>(it)
            SplashScreen(authViewModel, navController)
        }
        composable(Screens.Home.route) {
            val feedViewModel = hiltViewModel<FeedViewModel>()
            HomeFeedScreen(feedViewModel.state.value)
        }
    }
}

@Composable
fun SplashScreen(
    viewModel: AuthViewModel,
    navController: NavController
) {
    val state = viewModel.state.collectAsState()
    if (state.value.isSuccess) {
        navController.navigate(Screens.Home.route) {
            popUpTo(Screens.SplashScreen.route) {
                inclusive = true
            }
        }
    } else {
        // Render view...
    }
ViewModel:
Copy code
@HiltViewModel
class AuthViewModel
@Inject constructor(private val useCase: CreateAnonymousUserUseCase) :
    ViewModel() {
    private val _state: MutableStateFlow<AnonAuthResponse> = MutableStateFlow(AnonAuthResponse())
    val state: StateFlow<AnonAuthResponse> = _state

    init {
        crateAnonUser()
    }

    private fun crateAnonUser() {
        viewModelScope.launch {
            useCase.createAnonymousUser().collect {
                _state.value = _state.value.copy(isSuccess = it.isSuccess, loading = it.loading)
            }
        }
    }
}
Added delay instead of the actual network request to maket things easier:
Copy code
fun createAnonymousUser(): Flow<AnonAuthResponse> = flow {
    kotlinx.coroutines.delay(2500)
    emit(AnonAuthResponse(isSuccess = true))
}
If I remove the delay everything works fine, but when I add it it keeps on navigating to HomeScreen and re-rendering the SplashScreen. Thanks 🙏
1
i
You're calling
navigate()
as part of composition. That's always wrong (composition should never have side effects) - you'll end up calling
navigate()
on every frame of the animation between destinations, which is absolutely not what you want
But taking a step back, you should never have a splash screen destination as your start destination of your app as per the https://developer.android.com/guide/navigation/navigation-principles#fixed_start_destination
We've talked about login and these cases (and why they never work as the start destination of your app, particularly when you take into account process death and recreation and deep links) multiple times here in the past, such as: https://kotlinlang.slack.com/archives/CJLTWPH7S/p1626727460044100?thread_ts=1626726997.043700&amp;cid=CJLTWPH7S
2
c
Sorry to piggyback onto this, but Ian do you know if there's any sample that shows this sort of login scenario in compose. I basically have this working in my app because of all of your help, but I'm still"worried" that the way that I observe my loggedIn state and therefore triggering a move to the SignInScreen composable isn't right. Just trying to see if this is something that is already in some sample I can copy.
a
In the pathway tutorial, the final advanced segment, there's a section that deals with a splash screen.
c
Ooh. Thanks. Will take a look.
v
Thanks @Ian Lake on your detailed response, will take a look at resources about setting up the start destination correctly. Soo back to my original question, when we want to navigate somewhere when a state changes ( in this case upon successfull log in) if we don’t want to call navigate from compose should we move the navigation logic in our ViewModel? I guess something like what Joe Brich is talking in his blog post https://medium.com/google-developer-experts/modular-navigation-with-jetpack-compose-fda9f6b2bef7?
i
No, you don't have to do all that - processing events is something you do in one of the effect methods, such as `LaunchedEffect`:
Copy code
if (state.value.isSuccess) {
  // Use the value as the key
  // so that it only happens once
  LaunchedEffect(state.value) {
    // Call navigate here
  }
}
👍 1
v
Oh Interesting, thanks! 🙌🙌
c
@Ian Lake why wouldn't it be
LaunchedEffect(state.value.isSuccess)
?
v
I think it can, I believe it worked both ways for me
👍 1
i
Ideally you're using
by
and you wouldn't need the
.value
everywhere in the first place, but yes, I was assuming immutable objects where it wouldn't make a difference - the point is that the Launched effect would run just once for a given value change
🎉 2
☝️ 1
👍 1