Create User Registration & Login pages with Jetpack Compose Android studio
Last updated Sep 02, 2021 In this Jetpack compose tutorial we will learn how to create user registration and login form in Android application using Jetpack Compose. How to navigation from one screen to other screen by compose Navigation component
Let's get started
Step 1: Create android application in android studio
Step 2: Follow step for setup Jetpack Compose with Android Studio
Step 3: Add LoginScreen composable function
Our login screen contains Email and password fields and submit button
@Composable
fun LoginScreen() {
val context = LocalContext.current
val email = remember { mutableStateOf(TextFieldValue()) }
val emailErrorState = remember { mutableStateOf(false) }
val passwordErrorState = remember { mutableStateOf(false) }
val password = remember { mutableStateOf(TextFieldValue()) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
) {
Text(text = buildAnnotatedString {
withStyle(style = SpanStyle(color = Color.Red)) {
append("S")
}
withStyle(style = SpanStyle(color = Color.Black)) {
append("ign")
}
withStyle(style = SpanStyle(color = Color.Red)) {
append(" I")
}
withStyle(style = SpanStyle(color = Color.Black)) {
append("n")
}
}, fontSize = 30.sp)
Spacer(Modifier.size(16.dp))
OutlinedTextField(
value = email.value,
onValueChange = {
if (emailErrorState.value) {
emailErrorState.value = false
}
email.value = it
},
isError = emailErrorState.value,
modifier = Modifier.fillMaxWidth(),
label = {
Text(text = "Enter Email*")
},
)
if (emailErrorState.value) {
Text(text = "Required", color = Color.Red)
}
Spacer(Modifier.size(16.dp))
val passwordVisibility = remember { mutableStateOf(true) }
OutlinedTextField(
value = password.value,
onValueChange = {
if (passwordErrorState.value) {
passwordErrorState.value = false
}
password.value = it
},
isError = passwordErrorState.value,
modifier = Modifier.fillMaxWidth(),
label = {
Text(text = "Enter Password*")
},
trailingIcon = {
IconButton(onClick = {
passwordVisibility.value = !passwordVisibility.value
}) {
Icon(
imageVector = if (passwordVisibility.value) Icons.Default.VisibilityOff else Icons.Default.Visibility,
contentDescription = "visibility",
tint = Color.Red
)
}
},
visualTransformation = if (passwordVisibility.value) PasswordVisualTransformation() else VisualTransformation.None
)
if (passwordErrorState.value) {
Text(text = "Required", color = Color.Red)
}
Spacer(Modifier.size(16.dp))
Button(
onClick = {
when {
email.value.text.isEmpty() -> {
emailErrorState.value = true
}
password.value.text.isEmpty() -> {
passwordErrorState.value = true
}
else -> {
passwordErrorState.value = false
emailErrorState.value = false
Toast.makeText(
context,
"Logged in successfully",
Toast.LENGTH_SHORT
).show()
}
}
},
content = {
Text(text = "Login", color = Color.White)
},
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(backgroundColor = Color.Red)
)
Spacer(Modifier.size(16.dp))
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
TextButton(onClick = {
}) {
Text(text = "Register ?", color = Color.Red)
}
}
}
}
|
Add Registration Screen composable function
Registration page contains Name, Email, Mobile number, country code, password, confirm password and submit button
@Composable
fun RegistrationScreen() {
val context = LocalContext.current
val name = remember {
mutableStateOf(TextFieldValue())
}
val email = remember { mutableStateOf(TextFieldValue()) }
val countryCode = remember { mutableStateOf(TextFieldValue()) }
val mobileNo = remember { mutableStateOf(TextFieldValue()) }
val password = remember { mutableStateOf(TextFieldValue()) }
val confirmPassword = remember { mutableStateOf(TextFieldValue()) }
val nameErrorState = remember { mutableStateOf(false) }
val emailErrorState = remember { mutableStateOf(false) }
val passwordErrorState = remember { mutableStateOf(false) }
val confirmPasswordErrorState = remember { mutableStateOf(false) }
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.verticalScroll(scrollState),
verticalArrangement = Arrangement.Center,
) {
Text(text = buildAnnotatedString {
withStyle(style = SpanStyle(color = Color.Red)) {
append("R")
}
withStyle(style = SpanStyle(color = Color.Black)) {
append("egistration")
}
}, fontSize = 30.sp)
Spacer(Modifier.size(16.dp))
OutlinedTextField(
value = name.value,
onValueChange = {
if (nameErrorState.value) {
nameErrorState.value = false
}
name.value = it
},
modifier = Modifier.fillMaxWidth(),
isError = nameErrorState.value,
label = {
Text(text = "Name*")
},
)
if (nameErrorState.value) {
Text(text = "Required", color = Color.Red)
}
Spacer(Modifier.size(16.dp))
OutlinedTextField(
value = email.value,
onValueChange = {
if (emailErrorState.value) {
emailErrorState.value = false
}
email.value = it
},
modifier = Modifier.fillMaxWidth(),
isError = emailErrorState.value,
label = {
Text(text = "Email*")
},
)
if (emailErrorState.value) {
Text(text = "Required", color = Color.Red)
}
Spacer(modifier = Modifier.size(16.dp))
Row() {
OutlinedTextField(
value = countryCode.value,
onValueChange = {
countryCode.value = it
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
autoCorrect = false
),
modifier = Modifier.fillMaxWidth(0.3f),
label = {
Text(text = "Code")
},
)
Spacer(modifier = Modifier.size(16.dp))
OutlinedTextField(
value = mobileNo.value,
onValueChange = {
mobileNo.value = it
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Phone,
autoCorrect = false
),
label = {
Text(text = "Mobile No")
},
)
}
Spacer(Modifier.size(16.dp))
val passwordVisibility = remember { mutableStateOf(true) }
OutlinedTextField(
value = password.value,
onValueChange = {
if (passwordErrorState.value) {
passwordErrorState.value = false
}
password.value = it
},
modifier = Modifier.fillMaxWidth(),
label = {
Text(text = "Password*")
},
isError = passwordErrorState.value,
trailingIcon = {
IconButton(onClick = {
passwordVisibility.value = !passwordVisibility.value
}) {
Icon(
imageVector = if (passwordVisibility.value) Icons.Default.VisibilityOff else Icons.Default.Visibility,
contentDescription = "visibility",
tint = Color.Red
)
}
},
visualTransformation = if (passwordVisibility.value) PasswordVisualTransformation() else VisualTransformation.None
)
if (passwordErrorState.value) {
Text(text = "Required", color = Color.Red)
}
Spacer(Modifier.size(16.dp))
val cPasswordVisibility = remember { mutableStateOf(true) }
OutlinedTextField(
value = confirmPassword.value,
onValueChange = {
if (confirmPasswordErrorState.value) {
confirmPasswordErrorState.value = false
}
confirmPassword.value = it
},
modifier = Modifier.fillMaxWidth(),
isError = confirmPasswordErrorState.value,
label = {
Text(text = "Confirm Password*")
},
trailingIcon = {
IconButton(onClick = {
cPasswordVisibility.value = !cPasswordVisibility.value
}) {
Icon(
imageVector = if (cPasswordVisibility.value) Icons.Default.VisibilityOff else Icons.Default.Visibility,
contentDescription = "visibility",
tint = Color.Red
)
}
},
visualTransformation = if (cPasswordVisibility.value) PasswordVisualTransformation() else VisualTransformation.None
)
if (confirmPasswordErrorState.value) {
val msg = if (confirmPassword.value.text.isEmpty()) {
"Required"
} else if (confirmPassword.value.text != password.value.text) {
"Password not matching"
} else {
""
}
Text(text = msg, color = Color.Red)
}
Spacer(Modifier.size(16.dp))
Button(
onClick = {
when {
name.value.text.isEmpty() -> {
nameErrorState.value = true
}
email.value.text.isEmpty() -> {
emailErrorState.value = true
}
password.value.text.isEmpty() -> {
passwordErrorState.value = true
}
confirmPassword.value.text.isEmpty() -> {
confirmPasswordErrorState.value = true
}
confirmPassword.value.text != password.value.text -> {
confirmPasswordErrorState.value = true
}
else -> {
Toast.makeText(
context,
"Registered successfully",
Toast.LENGTH_SHORT
).show()
navController.navigate("login_screen") {
popUpTo(navController.graph.startDestinationId)
launchSingleTop = true
}
}
}
},
content = {
Text(text = "Register", color = Color.White)
},
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(backgroundColor = Color.Red)
)
Spacer(Modifier.size(16.dp))
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
TextButton(onClick = {
navController.navigate("login_screen") {
popUpTo(navController.graph.startDestinationId)
launchSingleTop = true
}
}) {
Text(text = "Login", color = Color.Red)
}
}
}
}
|
Now we are ready with Login and registration pages. How we could navigate from one screen to other screen with Compose library.
There we have one component called Navigation component which will be used to navigation from one screen to other screen.
To work with navigation component we need to add below dependencies
implementation "androidx.navigation:navigation-compose:2.4.0-alpha06"
|
The NavController of the Navigation component is statefull and it will keeps track of the back stack of screens in our app and the state of each screen.
We can create a NavController by using the rememberNavController() method in with composable
From official website
Each NavController is associated with a single NavHost composable. The NavHost links the NavController with a navigation graph that specifies the composable destinations that we should able to navigate between. When we navigate between composables, the content of the NavHost is automatically recomposed. Each composable destination in our navigation graph is associated with a route
Add below code in MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
setContent {
MaterialTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
LoginAndRegistration()
}
}
}
}
}
@Composable
fun LoginAndRegistration(){
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "login_screen", builder = {
composable("login_screen", content = { LoginScreen(navController = navController) })
composable("register_screen", content = { RegisterScreen(navController = navController) })
})
}
|
Now we need to pass this navigation controller to our login and registration composable
@Composable
fun LoginScreen(navController: NavController)
|
@Composable
fun RegistrationScreen(navController: NavController)
|
Add onClick functionality for Register button in Login Screen
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
TextButton(onClick = {
navController.navigate("register_screen") {
popUpTo(navController.graph.startDestinationId)
launchSingleTop = true
}
}) {
Text(text = "Register ?", color = Color.Red)
}
}
|
Add onClick functionality for Login button in Registration Screen
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
TextButton(onClick = {
navController.navigate("login_screen") {
popUpTo(navController.graph.startDestinationId)
launchSingleTop = true
}
}) {
Text(text = "Login", color = Color.Red)
}
}
|
Complete code
package com.example.jetpack
import android.os.Bundle
import android.view.WindowManager
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.jetpack.ui.theme.JetPackTheme
import com.example.jetpack.widget.CheckBoxDemo
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
setContent {
MaterialTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
LoginAndRegistration()
}
}
}
}
}
@Composable
fun LoginAndRegistration() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "login_screen", builder = {
composable("login_screen", content = { LoginScreen(navController = navController) })
composable(
"register_screen",
content = { RegistrationScreen(navController = navController) })
})
}
@Composable
fun LoginScreen(navController: NavController) {
val context = LocalContext.current
val email = remember { mutableStateOf(TextFieldValue()) }
val emailErrorState = remember { mutableStateOf(false) }
val passwordErrorState = remember { mutableStateOf(false) }
val password = remember { mutableStateOf(TextFieldValue()) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
) {
Text(text = buildAnnotatedString {
withStyle(style = SpanStyle(color = Color.Red)) {
append("S")
}
withStyle(style = SpanStyle(color = Color.Black)) {
append("ign")
}
withStyle(style = SpanStyle(color = Color.Red)) {
append(" I")
}
withStyle(style = SpanStyle(color = Color.Black)) {
append("n")
}
}, fontSize = 30.sp)
Spacer(Modifier.size(16.dp))
OutlinedTextField(
value = email.value,
onValueChange = {
if (emailErrorState.value) {
emailErrorState.value = false
}
email.value = it
},
isError = emailErrorState.value,
modifier = Modifier.fillMaxWidth(),
label = {
Text(text = "Enter Email*")
},
)
if (emailErrorState.value) {
Text(text = "Required", color = Color.Red)
}
Spacer(Modifier.size(16.dp))
val passwordVisibility = remember { mutableStateOf(true) }
OutlinedTextField(
value = password.value,
onValueChange = {
if (passwordErrorState.value) {
passwordErrorState.value = false
}
password.value = it
},
isError = passwordErrorState.value,
modifier = Modifier.fillMaxWidth(),
label = {
Text(text = "Enter Password*")
},
trailingIcon = {
IconButton(onClick = {
passwordVisibility.value = !passwordVisibility.value
}) {
Icon(
imageVector = if (passwordVisibility.value) Icons.Default.VisibilityOff else Icons.Default.Visibility,
contentDescription = "visibility",
tint = Color.Red
)
}
},
visualTransformation = if (passwordVisibility.value) PasswordVisualTransformation() else VisualTransformation.None
)
if (passwordErrorState.value) {
Text(text = "Required", color = Color.Red)
}
Spacer(Modifier.size(16.dp))
Button(
onClick = {
when {
email.value.text.isEmpty() -> {
emailErrorState.value = true
}
password.value.text.isEmpty() -> {
passwordErrorState.value = true
}
else -> {
passwordErrorState.value = false
emailErrorState.value = false
Toast.makeText(
context,
"Logged in successfully",
Toast.LENGTH_SHORT
).show()
}
}
},
content = {
Text(text = "Login", color = Color.White)
},
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(backgroundColor = Color.Red)
)
Spacer(Modifier.size(16.dp))
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
TextButton(onClick = {
navController.navigate("register_screen") {
popUpTo(navController.graph.startDestinationId)
launchSingleTop = true
}
}) {
Text(text = "Register ?", color = Color.Red)
}
}
}
}
@Composable
fun RegistrationScreen(navController: NavController) {
val context = LocalContext.current
val name = remember {
mutableStateOf(TextFieldValue())
}
val email = remember { mutableStateOf(TextFieldValue()) }
val countryCode = remember { mutableStateOf(TextFieldValue()) }
val mobileNo = remember { mutableStateOf(TextFieldValue()) }
val password = remember { mutableStateOf(TextFieldValue()) }
val confirmPassword = remember { mutableStateOf(TextFieldValue()) }
val nameErrorState = remember { mutableStateOf(false) }
val emailErrorState = remember { mutableStateOf(false) }
val passwordErrorState = remember { mutableStateOf(false) }
val confirmPasswordErrorState = remember { mutableStateOf(false) }
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.verticalScroll(scrollState),
verticalArrangement = Arrangement.Center,
) {
Text(text = buildAnnotatedString {
withStyle(style = SpanStyle(color = Color.Red)) {
append("R")
}
withStyle(style = SpanStyle(color = Color.Black)) {
append("egistration")
}
}, fontSize = 30.sp)
Spacer(Modifier.size(16.dp))
OutlinedTextField(
value = name.value,
onValueChange = {
if (nameErrorState.value) {
nameErrorState.value = false
}
name.value = it
},
modifier = Modifier.fillMaxWidth(),
isError = nameErrorState.value,
label = {
Text(text = "Name*")
},
)
if (nameErrorState.value) {
Text(text = "Required", color = Color.Red)
}
Spacer(Modifier.size(16.dp))
OutlinedTextField(
value = email.value,
onValueChange = {
if (emailErrorState.value) {
emailErrorState.value = false
}
email.value = it
},
modifier = Modifier.fillMaxWidth(),
isError = emailErrorState.value,
label = {
Text(text = "Email*")
},
)
if (emailErrorState.value) {
Text(text = "Required", color = Color.Red)
}
Spacer(modifier = Modifier.size(16.dp))
Row() {
OutlinedTextField(
value = countryCode.value,
onValueChange = {
countryCode.value = it
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
autoCorrect = false
),
modifier = Modifier.fillMaxWidth(0.3f),
label = {
Text(text = "Code")
},
)
Spacer(modifier = Modifier.size(16.dp))
OutlinedTextField(
value = mobileNo.value,
onValueChange = {
mobileNo.value = it
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Phone,
autoCorrect = false
),
label = {
Text(text = "Mobile No")
},
)
}
Spacer(Modifier.size(16.dp))
val passwordVisibility = remember { mutableStateOf(true) }
OutlinedTextField(
value = password.value,
onValueChange = {
if (passwordErrorState.value) {
passwordErrorState.value = false
}
password.value = it
},
modifier = Modifier.fillMaxWidth(),
label = {
Text(text = "Password*")
},
isError = passwordErrorState.value,
trailingIcon = {
IconButton(onClick = {
passwordVisibility.value = !passwordVisibility.value
}) {
Icon(
imageVector = if (passwordVisibility.value) Icons.Default.VisibilityOff else Icons.Default.Visibility,
contentDescription = "visibility",
tint = Color.Red
)
}
},
visualTransformation = if (passwordVisibility.value) PasswordVisualTransformation() else VisualTransformation.None
)
if (passwordErrorState.value) {
Text(text = "Required", color = Color.Red)
}
Spacer(Modifier.size(16.dp))
val cPasswordVisibility = remember { mutableStateOf(true) }
OutlinedTextField(
value = confirmPassword.value,
onValueChange = {
if (confirmPasswordErrorState.value) {
confirmPasswordErrorState.value = false
}
confirmPassword.value = it
},
modifier = Modifier.fillMaxWidth(),
isError = confirmPasswordErrorState.value,
label = {
Text(text = "Confirm Password*")
},
trailingIcon = {
IconButton(onClick = {
cPasswordVisibility.value = !cPasswordVisibility.value
}) {
Icon(
imageVector = if (cPasswordVisibility.value) Icons.Default.VisibilityOff else Icons.Default.Visibility,
contentDescription = "visibility",
tint = Color.Red
)
}
},
visualTransformation = if (cPasswordVisibility.value) PasswordVisualTransformation() else VisualTransformation.None
)
if (confirmPasswordErrorState.value) {
val msg = if (confirmPassword.value.text.isEmpty()) {
"Required"
} else if (confirmPassword.value.text != password.value.text) {
"Password not matching"
} else {
""
}
Text(text = msg, color = Color.Red)
}
Spacer(Modifier.size(16.dp))
Button(
onClick = {
when {
name.value.text.isEmpty() -> {
nameErrorState.value = true
}
email.value.text.isEmpty() -> {
emailErrorState.value = true
}
password.value.text.isEmpty() -> {
passwordErrorState.value = true
}
confirmPassword.value.text.isEmpty() -> {
confirmPasswordErrorState.value = true
}
confirmPassword.value.text != password.value.text -> {
confirmPasswordErrorState.value = true
}
else -> {
Toast.makeText(
context,
"Registered successfully",
Toast.LENGTH_SHORT
).show()
navController.navigate("login_screen") {
popUpTo(navController.graph.startDestinationId)
launchSingleTop = true
}
}
}
},
content = {
Text(text = "Register", color = Color.White)
},
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(backgroundColor = Color.Red)
)
Spacer(Modifier.size(16.dp))
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
TextButton(onClick = {
navController.navigate("login_screen") {
popUpTo(navController.graph.startDestinationId)
launchSingleTop = true
}
}) {
Text(text = "Login", color = Color.Red)
}
}
}
}
|
Now let's run application
Conclusion: In this jetpack compose tutorial we covered how to create Login and registration pages with compose and Navigation between screens by compose Navigation component.
Tags: Compose Login & Registration Page, Compose Navigation controller, How to navigate screens with compose, Compose Navigation controller