
Login - Logout Flow: Android Jetpack Compose and CompositionLocal
CompositionLocal is useful when you want to create a dependency in a higher node of the layout tree and use it on a lower node without having to pass it down the tree through every child Composable.
Here we will use it to direct our application when a user logs in and out.
- When the user is successfully logged in (isLoggedIn = true), they are directed to the rest of the application views.
- When a user is logged out (isLoggedIn = false) at any point within the app, they are directed to the login page.
We need the following components:
- User State View Model
- Application Switcher
- Login Screen
- Home Screen
User State View Model
The UserStateViewModel keeps track of and broadcasts our user status. We are going to store this view model to a CompositionLocal.
1import androidx.compose.runtime.compositionLocalOf 2import androidx.compose.runtime.getValue 3import androidx.compose.runtime.mutableStateOf 4import androidx.compose.runtime.setValue 5import androidx.lifecycle.ViewModel 6import kotlinx.coroutines.delay 7 8 9class UserStateViewModel : ViewModel() { 10 var isLoggedIn by mutableStateOf(false) 11 var isBusy by mutableStateOf(false) 12 13 suspend fun signIn(email: String, password: String) { 14 isBusy = true 15 delay(2000) 16 isLoggedIn = true 17 isBusy = false 18 } 19 20 suspend fun signOut() { 21 isBusy = true 22 delay(2000) 23 isLoggedIn = false 24 isBusy = false 25 } 26} 27 28val UserState = compositionLocalOf<UserStateViewModel> { error("User State Context Not Found!") } 29 30
We make our view model instance available to all child composables, starting from the ApplicationSwitcher composable
1class MainActivity : ComponentActivity() { 2 private val userState by viewModels<UserStateViewModel>() 3 override fun onCreate(savedInstanceState: Bundle?) { 4 super.onCreate(savedInstanceState) 5 setContent { 6 CompositionLocalProvider(UserState provides userState) { 7 ApplicationSwitcher() 8 } 9 } 10 } 11}
Application Switcher
1@Composable 2fun ApplicationSwitcher() { 3 val vm = UserState.current 4 if (vm.isLoggedIn) { 5 HomeScreen() 6 } else { 7 LoginScreen() 8 } 9}
Login Screen
The Login screen can now use the UserStateViewModel to invoke signIn
1@Composable 2fun LoginScreen() { 3 var email by remember { mutableStateOf("") } 4 var password by remember { mutableStateOf("") } 5 val coroutineScope = rememberCoroutineScope() 6 val vm = UserState.current 7 Column( 8 Modifier 9 .fillMaxSize() 10 .padding(32.dp), 11 verticalArrangement = Arrangement.Center, 12 horizontalAlignment = Alignment.CenterHorizontally 13 ) { 14 if (vm.isBusy) { 15 CircularProgressIndicator() 16 } else { 17 Text("Login Screen", fontSize = 32.sp) 18 Spacer(modifier = Modifier.height(16.dp)) 19 Email(email, onChange = { 20 email = it 21 }) 22 Spacer(modifier = Modifier.height(16.dp)) 23 Password(password, onChange = { 24 password = it 25 }) 26 Spacer(modifier = Modifier.height(16.dp)) 27 LoginButton(onClick = { 28 coroutineScope.launch { 29 vm.signIn(email, password) 30 } 31 }) 32 } 33 34 35 } 36}
Home Screen with Logout Button
The Home screen also uses the UserStateViewModel to invoke the signOut
1@Composable 2fun HomeScreen(navController: NavHostController) { 3 val vm = UserState.current 4 val coroutineScope = rememberCoroutineScope() 5 Scaffold( 6 topBar = { 7 TopAppBar( 8 title = { Text("Home") }, 9 actions = { 10 IconButton(onClick = { 11 coroutineScope.launch { 12 vm.signOut() 13 } 14 }) { 15 Icon(Icons.Filled.ExitToApp, null) 16 } 17 } 18 ) 19 }, 20 ) { 21 Column( 22 Modifier 23 .fillMaxSize() 24 .padding(16.dp), 25 verticalArrangement = Arrangement.Center, 26 horizontalAlignment = Alignment.CenterHorizontally 27 ) { 28 if (vm.isBusy) { 29 CircularProgressIndicator() 30 } else { 31 Text("Home") 32 } 33 } 34 } 35}
At all times the Application Switcher is monitoring the isLoggedIn status: