What is Android Jetpack Compose

The official website explains more comprehensively, what is declarative UI? My personal understanding is that it is the toolkit used by Android to build UI. It simplifies and speeds up UI development on Android. But in order to develop with it, you need to know Kotlin.

So without further ado, let’s create a new Android project.

1. Create a new Android Compose project

First open Android Studio and create a new project. The version of Android Studio I use is Android Studio Hedgehog | 2023.1.1 Patch 2, select “Empty Activity” and click “Next”.

Next you will see the following interface, enter “FormExample” as the project name, and then click “Finish”.

2. Add the required packages

We will be using this in this example navigation-compose, so open the module build.gradlefile and add the following implementation:

implementation("androidx.navigation:navigation-compose:2.7.0")

3. Create MainActivity

Sync your project and open it MainActivity.kt.

Paste/enter the following code:

package com.example.formexample

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.formexample.pages.ForgotPassword
import com.example.formexample.pages.LoginPage
import com.example.formexample.pages.SignUp
import com.example.formexample.ui.theme.FormExampleTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            FormExampleTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    ScreenMain()
                }
            }
        }
    }
}

@Composable
fun ScreenMain() {
    val navController = rememberNavController()

    NavHost(navController = navController, startDestination = "login") {
        composable("login") { LoginPage(navController) }
        composable("signup") { SignUp(navController) }
        composable("forgot-password") { ForgotPassword(navController) }
    }
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    FormExampleTheme {
        ScreenMain()
    }
}

Here we basically have a home screen set up and a navigation controller to navigate between multiple pages. Following the page is the login screen. Don’t worry about any errors at this point, as we’ll create the required pages shortly.

4. Create a custom top bar

Next create a new “Components” package and create the file CustomTopAppBar.ktand paste/enter the following code:

package com.example.formexample.component

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.navigation.NavController

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomTopAppBar(navController: NavController, title: String, showBackIcon: Boolean) {
    TopAppBar(
        title = { Text(text = title) },
        navigationIcon = {
            if (showBackIcon && navController.previousBackStackEntry != null) {
                IconButton(onClick = { navController.navigateUp() }) {
                    Icon(
                        Icons.Filled.ArrowBack,
                        contentDescription = "Back"
                    )
                }
            }
        }
    )
}

Here we basically set up the application top bar and set the title and if showBackIcon is enabled then a back button will be shown which when pressed the user can return to the top screen.

5. Create a page

Next create a new pagespackage. Create ForgotPassword.kta file and paste/enter the following:

package com.example.formexample.pages

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.example.formexample.component.CustomTopAppBar

@Composable
fun ForgotPassword(navController: NavHostController) {
    Box(modifier = Modifier.fillMaxSize()) {
        Scaffold(
            topBar = { CustomTopAppBar(
                navController = navController,
                title = "Forgot Password",
                showBackIcon = true
            )},
            content = { it ->
                Column(
                    modifier = Modifier.padding(it),
                    verticalArrangement = Arrangement.Center,
                    horizontalAlignment = Alignment.CenterHorizontally,
                ) {
                    val username = remember {
                        mutableStateOf(TextFieldValue())
                    }
                    Text(
                        text = "Forgot Password",
                        style = TextStyle(fontSize = 40.sp, fontFamily = FontFamily.Cursive)
                    )

                    Spacer(modifier = Modifier.height(15.dp))

                    TextField(
                        label = { Text(text = "Username") },
                        value = username.value,
                        onValueChange = { username.value = it }
                    )
                    
                    Spacer(modifier = Modifier.height(15.dp))

                    Box(modifier = Modifier.padding(40.dp, 0.dp, 40.dp, 0.dp)) {
                        Button(
                            onClick = { },
                            shape = RoundedCornerShape(50.dp),
                            modifier = Modifier
                                .fillMaxWidth()
                                .height(50.dp)
                        ) {
                            Text(text = "Forgot Password")
                        }
                    }
                }
            }
        )
    }
}

@Preview(showBackground = true)
@Composable
fun ForgotPasswordPreview() {
    val navController = rememberNavController()
    ForgotPassword(navController)
}

Here we create a simple form using compose’s ui elements. Use beautiful fonts, text input for users to enter their username, and clickable round buttons.

Next we will create the login form, so create a Login.ktfile called and enter/paste the following code:

package com.example.formexample.pages

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.example.formexample.ui.theme.Purple80

@Composable
fun LoginPage(navController: NavHostController) {
    Box(modifier = Modifier.fillMaxSize()) {
        ClickableText(
            text = AnnotatedString("Signup Here"),
            modifier = Modifier
                .align(Alignment.Center)
                .padding(20.dp),
            onClick = {
                navController.navigate("signup")
            },
            style = TextStyle(
                fontSize = 14.sp,
                fontFamily = FontFamily.Default,
                textDecoration = TextDecoration.Underline,
                color = Purple80
            )
        )
    }
    Box(
        Modifier.wrapContentHeight(Alignment.Top)
    ) {
        Column(
            modifier = Modifier.padding(20.dp),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally,
        ) {
            val username = remember {
                mutableStateOf(TextFieldValue())
            }
            val password = remember {
                mutableStateOf(TextFieldValue())
            }

            Text(
                text = "Login",
                style = TextStyle(fontSize = 40.sp, fontFamily = FontFamily.Cursive)
            )

            Spacer(modifier = Modifier.height(15.dp))

            TextField(
                label = { Text(text = "Username") },
                value = username.value,
                onValueChange = { username.value = it },
                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
            )

            Spacer(modifier = Modifier.height(15.dp))

            TextField(
                label = { Text(text = "Password") },
                value = password.value,
                onValueChange = { password.value = it },
                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
            )

            Spacer(modifier = Modifier.height(15.dp))

            Box(modifier = Modifier.padding(40.dp, 0.dp, 40.dp, 0.dp)) {
                Button(
                    onClick = {},
                    shape = RoundedCornerShape(50.dp),
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(50.dp)
                ) {
                    Text(text = "Login")
                }
            }

            Spacer(modifier = Modifier.height(15.dp))

            ClickableText(
                text = AnnotatedString("Forgot Password?"),
                onClick = { navController.navigate("forgot-password") },
                style = TextStyle(
                    fontSize = 15.sp,
                    fontFamily = FontFamily.Default
                )
            )
        }
    }
}

@Preview
@Composable
fun LoginPagePreview() {
    val navController = rememberNavController()
    LoginPage(navController = navController)
}

This will provide a basic login form using Compose’s UI elements which provide a link for the user to register and another link if the user has forgotten their password.

Finally, we will create the final form, create SignUp.ktthe file and paste/enter the following code:

package com.example.formexample.pages

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.example.formexample.component.CustomTopAppBar

@Composable
fun SignUp(navController: NavHostController) {
    Scaffold(
        topBar = { CustomTopAppBar(
            navController = navController,
            title = "SignUp",
            showBackIcon = true)
        },
        content = { it ->
            Column(
                modifier = Modifier.padding(it),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                val username = remember {
                    mutableStateOf(TextFieldValue())
                }
                
                val password = remember {
                    mutableStateOf(TextFieldValue())
                }
                
                val passwordConfirm = remember {
                    mutableStateOf(TextFieldValue())
                }
                
                Text(
                   text = "SignUp", 
                    style = TextStyle(fontSize = 40.sp, fontFamily = FontFamily.Cursive)
                )
                
                Spacer(modifier = Modifier.height(15.dp))
                
                TextField(
                    label = { Text(text = "Username")},
                    value = username.value, 
                    onValueChange = { username.value = it}
                )
                
                Spacer(modifier = Modifier.height(15.dp))
                
                TextField(
                    label = { Text( text = "Password") },
                    value = password.value, 
                    onValueChange = { password.value = it},
                    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
                )
                
                Spacer(modifier = Modifier.height(15.dp))

                TextField(
                    label = {Text(text = "Password Confirmation")},
                    value = passwordConfirm.value, 
                    onValueChange =  { passwordConfirm.value = it },
                    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
                )
                
                Spacer(modifier = Modifier.height(15.dp))

                Box(modifier = Modifier.padding(40.dp, 0.dp, 40.dp, 0.dp)) {
                    Button(
                        onClick = {},
                        shape = RoundedCornerShape(50.dp),
                        modifier = Modifier
                            .fillMaxWidth()
                            .height(50.dp)
                    ) {
                        Text(text = "SignUp")
                    }
                }
            }
        }
    )
}

@Preview
@Composable
fun SignUpPreview() {
    val navController = rememberNavController()
    SignUp(navController = navController)
}

Again, nothing too complicated, this form contains fields for username and password/confirm password input text.

Next, we can finally check out the UI and navigation, fire up the emulator/actual device and you should be able to see/navigate the screen:

forget the password:

Log in:

register:

summary

Here I show how to create a simple registration form using Android Jetpack Comppose.

Hope this helps anyone who wants to try Android Jetpack Compose.

Leave a Reply

Your email address will not be published. Required fields are marked *