February 22

0 comments

Create Android App Login UI with Jetpack Compose

By Val Okafor

February 22, 2023

Android Login UI, Jetpack Compose

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.

Screenshot of generic login screen for Android app built with Jetpack Compose
Figure 1 – Pronto Login UI App screenshot

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 dependencies

You 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.

Annotated Login Screen UI showing Jetpack Compose components
Figure 2 – Annotated Pronto Login UI App screenshot

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 Composable

Add 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 Composable

In 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 color

Add 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 color

If you preview the composable, you will see a blank screen with a circular ring at the top, as shown in Figure 1.3

Example adding image to Jetpack Compose
Figure 3 – Box Layout with an image aligned top right

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 Logo

If 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.

Figure 1.4 – Column Layout with an image centered

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 states

As 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

Figure 1.5 – Email Input Text

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 visibility

With 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 icon

Now, 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 TextField

Add 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 password

Add 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 Button

Add 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 Row

Summary

We created an Android Ui for a login screen in today’s tutorial. The complete source code can be found on Github.

Val Okafor

About the author

I am a family-first Software Engineer and Creator. I create mobile-first apps that grow businesses and increase efficiency. I learn and share my journey figuring out the money side of code. It is written "Man shall not live by code alone"

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

Never miss a good story!

 Subscribe to our newsletter to keep up with the latest trends!

>