Today I will create a simple Android app login screen called Pronto Login. This login screen is a perfect introduction to Jetpack Compose because it touches on some core concepts of Jetpack Compose while remaining close to the mental model of View-based UI development. You can immediately look at the screen and instantly recognize how you can crank this out with Constraint Layout and/or LinearLayout.

Before we jump into Android Studio to create this UI, feel free to go over the list of twenty Jetpack Compose keywords that you will commonly run into in Compose land.
Create New Project
For this login screen UI. I created a new Android Studio project and selected the Empty Compose Activity template. This template uses Material Design 2, and I named this project Pronto Login.
Once the project is created, you should update the project’s dependencies to use the desired Jetpack Compose compiler and version. Jetpack Compose libraries are shipped/updated separately. Instead of managing the versions of each library separately, you can now use a Bill of Material (BOM) to keep the versions of all the libraries in sync.
You also need to make sure that you are using the correct Jetpack Compose compiler version for your Kotlin version. Use the Compose to Kotlin Compatibility map to find the correct version for your project. For this project, I am using.
- Android Studio version: Electric Eel
- Kotlin version: 1.7.20
- Compose compiler version: 1.3.2
- Compose BOM version: 2022.12.00
A redacted view of my app’s build.gradle looks like the snippet shown in Code Sample 1
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.valokafor.prontologin'
compileSdk 33
composeOptions {
kotlinCompilerExtensionVersion '1.3.2'
}
}
dependencies {
def composeBom = platform('androidx.compose:compose-bom:2022.12.00')
implementation composeBom
debugImplementation 'androidx.compose.ui:ui-tooling'
implementation "androidx.compose.ui:ui"
implementation "androidx.compose.ui:ui-tooling-preview"
implementation "androidx.compose.material:material"
implementation "androidx.compose.material:material-icons-extended"
implementation 'androidx.activity:activity-compose:1.6.1'
}
Code Sample 1.1 – Redacted App build.gradle showing Jetpack Compose dependenciesYou can download the starter project, which has the dependencies and resources for this project.
Create Login Screen
You can keep the Figma design of the login screen handy as a reference as you work through this tutorial. This screen could easily be created using a Column layout, however, notice that it has a circular ring at the top right corner. For this reason, we will use the Box layout as the root layout and then nest a Column layout inside it. The screenshot below shows an annotated screenshot of the Pronto Login UI.

At the root of the project, add a Kotlin file named LoginScreen.kt, inside this file, add a Composable function with the same name. Begin by adding a Box layout with a modifier of fillMaxSize
screen as shown in Code Sample 2.
@Composable
fun LoginScreen() {
Box(modifier = Modifier.fillMaxSize()) {
//Screen content goes here
}
}
Code Sample 1.2 – Empty Login Screen ComposableAdd a Preview
Below this composable function, add another composable function with a preview annotation. This will enable you to preview the screen while you construct it. Code Sample 3 shows an example preview composable function.
@Preview
@Composable
fun LoginScreenPreview() {
LoginScreen()
}
Code Sample 1.3 – Empty Login Screen ComposableIn this tutorial, we focus on creating this Login UI and will not get into Material Design. Therefore, for this reason, we will leave the default app theme and colors as-is. Instead, update the Color.kt file to add the colors that apply to this project, as shown in Code Sample 1.4
val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFF6200EE)
val Purple700 = Color(0xFF3700B3)
val Teal200 = Color(0xFF03DAC5)
val BackgroundColor = Color(0xFFE5E5E5)
val AppBlackColor = Color(0xFF393F45)
val AppFocusColor = Color(0xFFABB3BB)
val AppUnFocusedColor = Color(0xFFD0D0D0)
val AppBlueColor = Color(0xFF2945FF)
val FacebookButtonColor = Color(0xFF298FFF)
val GoogleButtonColor = Color(0xFFFC6A57)
Code Sample 1.4 – Update color With this change, made, you can now update the Login screen to include a background color, as shown in Code Sample 1.5
@Composable
fun LoginScreen() {
Box(modifier = Modifier
.fillMaxSize()
.background(BackgroundColor)
) {
//Screen content goes here
}
}
Code Sample 1.5 – Box with app background colorAdd Top Circular Ring
This Login UI has a circular ring at the top right-hand corner. We could use ContraintLayout, or a combination of Column/Row with padding to place this image on the screen. However, I have chosen to use Box with top-end alignment, as shown in Code Sample 1.6
@Composable
fun LoginScreen() {
Box(modifier = Modifier
.fillMaxSize()
.background(BackgroundColor)
) {
//Screen content goes here
}
}
Code Sample 5 – Box with app background colorIf you preview the composable, you will see a blank screen with a circular ring at the top, as shown in Figure 1.3

Add Column layout and Logo
The rest of the screen’s content will be placed inside a Column layout which is nested in the Box layout. The Column’s content will be centered horizontally, starting with the logo. Update the Login screen to add the Column and logo as shown in Code Sample 1.7
@Composable
fun LoginScreen() {
Box(modifier = Modifier
.fillMaxSize()
.background(BackgroundColor)
) {
//Redacted Content
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(horizontal = 24.dp, vertical = 32.dp)
) {
Image(
painter = painterResource(id = R.drawable.login_logo),
contentDescription = "Sample Logo",
modifier = Modifier.padding(vertical = 24.dp)
)
}
}
}
Code Sample 1.7 – Column showing LogoIf you run the app, you will see both images displayed on the screen, as shown in Figure 1.4. The dimensions used are derived from the Figma design shared above. The drawables are included in the starter project, which is downloadable from the link shared earlier.

Add Header Texts
Next, add the two texts “Welcome Back” and “Sign In to Continue” under the image. We are currently styling these texts inline and not applying material design style. Notice the user of Spacer to add spacing between the texts as shown in Code Sample 1.8
Text (
text = stringResource(id = R.string.welcome_back),
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Serif,
fontSize = 28.sp,
color = AppBlackColor
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(id = R.string.sign_in_continue),
fontWeight = FontWeight.Normal,
fontFamily = FontFamily.Serif,
fontSize = 16.sp,
color = AppBlackColor
)
Code Sample 1.8 – Add Text composable Add Email Field
We will use two states to provide the values that should be shown in the email and password fields. For this demo, we will hold those states in two mutable states referenced as email
and password
. In real projects, these states could be passed in. The role of the UI should be to display the state and not manage it.
@Composable
fun LoginScreen() {
var email by remember { mutableStateOf(TextFieldValue("")) }
var password by remember { mutableStateOf(TextFieldValue("")) }
Box(modifier = Modifier
.fillMaxSize()
.background(BackgroundColor)
) {
//Content redacted
}
}
Code Sample 1.9 – Email and Password mutable statesAs the user is typing, we will update the value of the states. In a production app, as the user is typing, we could ideally send the input to a ViewModel for processing and/or validation. Jetpack Compose OutlinedTextField is similar to the Views InputLayout. Notice the label, placeholder, and leading icon properties as shown in Figure 1.5

Add Password Field
The design for this login screen has a visibility toggle for the password field. We need to track this with a state. For this reason, add another state to the top of the file called passwordVisible
with a default value of false, as shown in Code Sample 1.10
var passwordVisible by rememberSaveable { mutableStateOf(false) }
Code Sample 1.10 – state to track password visibilityWith the state added, we now need to track that state to determine which icon to show as the trailing icon for the password field, as shown in Code Sample 1.11
val visibilityIcon = if (passwordVisible) Icons.Filled.Visibility else Icons.Filled.VisibilityOff
val description = if (passwordVisible) "Hide password" else "Show password"
Code Sample 1.11 – track password visibility iconNow, you can add the password field similar to the email field as shown in Code Sample 1.12. Notice the use of a visual transformation parameter to control the visibility of password input.
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text(text = stringResource(id = R.string.label_password)) },
placeholder = { Text(text = stringResource(id = R.string.hint_enter_password)) },
shape = RoundedCornerShape(8.dp),
colors = TextFieldDefaults.outlinedTextFieldColors(
focusedBorderColor = AppFocusColor,
unfocusedBorderColor = AppUnFocusedColor
),
leadingIcon = {
Icon(
imageVector = Icons.Default.Lock,
contentDescription = "Email Icon",
tint = AppFocusColor
)
},
modifier = Modifier.fillMaxWidth(),
visualTransformation = if (passwordVisible) VisualTransformation.None
else PasswordVisualTransformation(),
trailingIcon = {
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(imageVector = visibilityIcon, description)
}
}
)
Code Sample 1.12 – Password Outlined TextFieldAdd Forgot Password Row
We need another state to track whether the remember password checkbox is checked. So add this val checkedState = remember {mutableStateOf*(false)}
state to the top of the file. We use a nested Row to hold the Checkbox and the “Remember Me” text and align with the “Forgot Password” text as shown in Code Sample 1.13
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(
checked = checkedState.value,
onCheckedChange = { checkedState.value = it })
Text(
text = stringResource(id = R.string.remember_me),
fontSize = 14.sp,
color = AppFocusColor)
}
Spacer(modifier = Modifier.weight(1f))
Text(
text = stringResource(id = R.string.forgot_password),
fontSize = 14.sp,
color = AppBlueColor,
modifier = Modifier.clickable { }
)
}
Code Sample 1.13 – Nested row showing checkbox and forgot passwordAdd Login Button
The button composable is used to add the buttons on the screen. The starter project contains the color for the buttons. We are not adding the click handlers for the button at this time. Figure 1.14 shows the sign-in button.
Button(
onClick = { },
shape = RoundedCornerShape(8.dp),
colors = ButtonDefaults.buttonColors(backgroundColor = AppBlueColor),
modifier = Modifier
.fillMaxWidth()
.height(60.dp)
) {
Text(
text = stringResource(id = R.string.sign_in),
style = TextStyle(
color = Color.White,
fontSize = 14.sp,
fontWeight = FontWeight.Bold
)
)
}
Code Sample 1.14 – Sign In ButtonAdd Social Buttons
The Facebook and Google buttons are also added using Button components. The difference is that we used a Row to hold the icon and the text inside the button, as shown in Code Sample 1.15
Button(
onClick = { },
shape = RoundedCornerShape(8.dp),
colors = ButtonDefaults.buttonColors(backgroundColor = GoogleButtonColor),
modifier = Modifier.height(45.dp).fillMaxWidth().weight(0.5f)
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
painter = painterResource(id = R.drawable.google_icon),
contentDescription = "Google icon",
tint = Color.White
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = "Google",
style = TextStyle(color = Color.White, fontSize = 12.sp,)
)
}
}
Code Sample 1.15 – Social Button RowSummary
We created an Android Ui for a login screen in today’s tutorial. The complete source code can be found on Github.