Reputation: 2786
My application uses OpenID to authenticate the user.
The first page is more of a splash screen that hands the user to a webpage to authorise if needed or just perform a background refresh of the token the navigate to the main screen.
I'm unsure of how to start the authentication flow without a button click
@Composable
fun LoginScreen(viewModel: LoginViewModel) {
val ctx = LocalContext.current
AppTheme {
Screen()
}
viewModel.performLogin(ctx)
}
Doing the above works, but it then gets called again when the app navigates to the main screen.
fun loginComplete(navController: NavHostController) {
navController.navigate("main")
}
@Composable
fun MyApp(viewModel: LoginViewModel) {
val navController = rememberNavController()
viewModel.setOnLoginCompete(navController, ::loginComplete)
NavHost(navController, startDestination = "login") {
composable(route = "login") {
LoginScreen(viewModel)
}
composable(route = "main") {
MainScreen()
}
}
}
I don't think I'm supposed to call the performLogin function like I am in a Composable function, but I can't see another way. What am I missing?
Upvotes: 12
Views: 16300
Reputation: 7804
If you only want to launch a method on your VM once your screen is composed for the first time, you can use LaunchedEffect
with Unit
as the key. This block will execute when first entering the composition.
It'll also re-execute if the node exits and re-enters the composition, for eg on activity recreation.
LaunchedEffect(Unit) {
viewModel.onEnteredComposition()
}
Upvotes: 1
Reputation: 715
Another way you can use is as follows
Let's do it step by step :)
First Step : the ViewModel must implement the DefaultLifecycleObserver interface
@HiltViewModel
class LoginViewModel @Inject constructor(
private val savedStateHandle: SavedStateHandle
) : ViewModel(), DefaultLifecycleObserver {
}
in this way you can acess this methods:
public interface DefaultLifecycleObserver extends FullLifecycleObserver {
/**
* Notifies that {@code ON_CREATE} event occurred.
* <p>
* This method will be called after the {@link LifecycleOwner}'s {@code onCreate}
* method returns.
*
* @param owner the component, whose state was changed
*/
@Override
default void onCreate(@NonNull LifecycleOwner owner) {
}
/**
* Notifies that {@code ON_START} event occurred.
* <p>
* This method will be called after the {@link LifecycleOwner}'s {@code onStart} method returns.
*
* @param owner the component, whose state was changed
*/
@Override
default void onStart(@NonNull LifecycleOwner owner) {
}
/**
* Notifies that {@code ON_RESUME} event occurred.
* <p>
* This method will be called after the {@link LifecycleOwner}'s {@code onResume}
* method returns.
*
* @param owner the component, whose state was changed
*/
@Override
default void onResume(@NonNull LifecycleOwner owner) {
}
/**
* Notifies that {@code ON_PAUSE} event occurred.
* <p>
* This method will be called before the {@link LifecycleOwner}'s {@code onPause} method
* is called.
*
* @param owner the component, whose state was changed
*/
@Override
default void onPause(@NonNull LifecycleOwner owner) {
}
/**
* Notifies that {@code ON_STOP} event occurred.
* <p>
* This method will be called before the {@link LifecycleOwner}'s {@code onStop} method
* is called.
*
* @param owner the component, whose state was changed
*/
@Override
default void onStop(@NonNull LifecycleOwner owner) {
}
/**
* Notifies that {@code ON_DESTROY} event occurred.
* <p>
* This method will be called before the {@link LifecycleOwner}'s {@code onDestroy} method
* is called.
*
* @param owner the component, whose state was changed
*/
@Override
default void onDestroy(@NonNull LifecycleOwner owner) {
}
}
Second Step: In your Composable, call the method that I have sent to the attachment as below.
create an extension function like this:
@Composable
fun <LO : LifecycleObserver> LO.observeLifecycle(lifecycle: Lifecycle) {
DisposableEffect(lifecycle) {
lifecycle.addObserver(this@observeLifecycle)
onDispose {
lifecycle.removeObserver(this@observeLifecycle)
}
}
}
Call it in your composable as follows
@Composable
fun LoginScreen(viewModel: LoginViewModel) {
viewModel.observeLifecycle(LocalLifecycleOwner.current.lifecycle)
}
Thirst Step: Implement your logic in ViewModel and if you are going to go to another page, inform Composable by Event
LoginViewModel.kt
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
}
Upvotes: 3
Reputation: 3226
You can tie your flow to lifecycle callbacks. You can create utility composable for handling lifecycle events.
@Composable
fun OnLifecycleEvent(onEvent: (owner: LifecycleOwner, event:Lifecycle.Event) -> Unit) {
val eventHandler = rememberUpdatedState(onEvent)
val lifecycleOwner = rememberUpdatedState(LocalLifecycleOwner.current)
DisposableEffect(lifecycleOwner.value) {
val lifecycle = lifecycleOwner.value.lifecycle
val observer = LifecycleEventObserver { owner, event ->
eventHandler.value(owner, event)
}
lifecycle.addObserver(observer)
onDispose {
lifecycle.removeObserver(observer)
}
}
}
And the use it like this, if you want to start logging flow when your app comes to the foreground:
@Composable
fun MyApp(viewModel: LoginViewModel) {
...
OnLifecycleEvent { owner, event ->
when (event) {
Lifecycle.Event.ON_RESUME -> { viewModel.performLogin(ctx) }
else -> { ... }
}
}
}
I actually used this question and its answer as the source
Upvotes: 24