Login - Logout Flow: Android Jetpack Compose and CompositionLocal
Photo by James Wheeler from Pexels

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.

  1. When the user is successfully logged in (isLoggedIn = true), they are directed to the rest of the application views.
  2. When a user is logged out (isLoggedIn = false) at any point within the app, they are directed to the login page.

1_6VAswSMtIhBP6zBwzvPShg.png

We need the following components:

  1. User State View Model
  2. Application Switcher
  3. Login Screen
  4. 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:

1_8eRY-tsLv8eDl2JkzSmybw.gif