February 22

0 comments

Day 1 – Master Jetpack Compose in 100 Days

By Val Okafor

February 22, 2023

Android Login UI, Jetpack Compose

Today I begin a tutorial series I am calling Pronto Compose – Master Jetpack Compose in 100 Days. In this series, I will share lessons learned, tips, and tutorials that will help you become more productive with Jetpack Compose.

I have now used Jetpack Compose in three production apps published to the Play Store. Two of the apps are client work and one is a full re-write of my side hustle app Pronto Invoice. From these projects and numerous hobby projects, I have experienced firsthand, the productivity gains that are possible when you complete the mental shift from Fragment/XML/View-based Android UI development to declarative UI development with Jetpack Compose.

I have to admit, the learning curve is steeper than I anticipated. This is not anything like the other switches we had to make as Android developers such as the switch from Java to Kotlin or Eclipse to Android Studio. This is truly a paradigm shift. A shift that cannot happen by creating a couple of Hello World apps.

At this time, I feel very proficient with Jetpack Compose and I want to go from proficiency to mastery so I invite you on this journey with me if you plan to remain a modern Android developer. I suspect that the longer you wait to adopt Jetpack Compose, soon you will have to re-learn Android development all over again or seek other career paths.

In this series, I will create over ten basic but complete Android apps such as Notepad App, Todo List app, Movie Listing app, and Shopping Cart app to name just a few. Many of these apps are apps that I already have previously built with legacy Fragment and XML. I will be re-writing them using modern Android development tools such as Jetpack Compose, Material Design 3, MVVM, Hilt, Coroutine flow, Firebase tooling, etc., and sharing the process.

Here are some screenshots of the Android apps that I will be creating in this tutorial series.

Screenshots of featured app for the Pronto Compose tutorial series
Figure 1.1 – Sample Pronto Compose apps screenshots

Jetpack Compose Learning Resources

This tutorial series is not intended to be a substitution for the official Jetpack Compose tutorial. You still need to read the manual. At the minimum, I highly encourage you to read the official introductory lesson and take at least the first two of the Jetpack Compose pathway lesson. The tutorial series is meant to supplement the introductory lessons with real-world applications.

While I will cover extensive Jetpack Compose APIs and components, I will assume that you have at least taken the introductory Compose lesson.

Other Jetpact Compose learning resources include

Introducing Day 1 Featured App – Pronto Login

Today I will create a simple Android app login screen called Pronto Login. This is the UI only.

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

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 immediately 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 which is sufficient for an introduction to Jetpack Compose. In subsequent tutorials, I will start using Material Design 3 templates. I named this project Pronto Login.

Once the project is created, you will need to update the dependencies to use the desired Jetpack Compose compiler and version. Jetpack Compose libraries are shipped/updated separately and instead of managing the versions of each library separately, you 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.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 as well as the resources for this project from here.

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 Box as the root layout and then next 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 1.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 fill the max screen as shown in Code Sample 1.2.

@Composable
fun LoginScreen() {
    Box(modifier = Modifier.fillMaxSize()) {
        //Screen content goes here
    }
}
Code Sample 1.2 – Empty Login Screen Composable

Add 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 1.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 provide 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 1.3 – Box Layout with image aligned top right

Add Column and Logo

The rest of the content of the screen will be placed inside a Column layout which is nested in the Box layout. All the content of the Column 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 that 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 styling these texts inline and not applying material design style at this time. 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 the composable. In real projects, these states could be passed in. The role of the UI should b to display the state and not manage it.

Add two mutable states named email and password at the opt of the file as shown in Code Sampl e 1.9

@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 be sending 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 or not. 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 composable. 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

In today’s tutorial we have covered key Jetpack Compose key terms and keywords. We created an Android Ui for a login screen. 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!

>