Android Jetpack Compose – Design User Profile UI
Jetpack Compose is a modern Android UI toolkit by Google that simplifies the process of building beautiful and responsive user interfaces. It allows developers to create UI components using a declarative approach, making it easier to build and maintain complex UIs while providing a seamless user experience. In this article, we will show you how to create a User Profile UI using Jetpack Compose.
Prerequisites:
- Familiar with Kotlin
- Basic understanding of Jetpack Compose
- Android Studio
So, we are just going to create a User Profile UI using Jetpack compose (Kotlin). our final UI will look like it.
Step-by-Step Implementation
Step 1: Create a new android studio project
The first step is to create a new Android Studio project. To create a new project using Jetpack Compose please refer to How to Create a New Project in Android Studio Canary Version with Jetpack Compose.
Step 2: Add dependencies
Once the project is created, you will need to add the following dependencies to your build.gradle
file
dependencies {
implementation("androidx.compose.material3:material3:1.1.0")
}
Step 3: Create a Kotlin class ProfileScreen.kt
The ProfileScreen consists of three parts: the user’s details, popular content, and options. The user’s details include the user’s image, name, and email address, etc. Popular content includes famous posts and other users’ activity details. The options include a list of buttons that allow the user to access different features of the app.
Kotlin
import androidx.compose.runtime.Composable @Composable fun ProfileScreen(onGoBack: () -> Unit) { } |
Step 4: Create another Kotlin data class ProfileModel.kt
Kotlin
data class ProfilePopularList( val name: String, val description: String, val star: String, val language: String ) data class ImageTextList( val icon: DCodeIcon, val text: String ) |
Step 5: Create another Kotlin class MyIcons.kt
Kotlin
/** * Damahe Code icons. Material icons are [ImageVector]s, custom icons are drawable resource IDs. */ object MyIcons { val List = Icons.Rounded.List val Info = Icons.Rounded.Info val AccountBox = Icons.Default.AccountBox val Location = Icons.Rounded.LocationOn val ArrowBack = Icons.Filled.ArrowBack val Search = Icons.Filled.Search val MoreVert = Icons.Filled.MoreVert val Star = Icons.Filled.Star val Email = Icons.Filled.Email val Share = Icons.Filled.Share val Edit = Icons.Filled.Edit val KeyboardArrowRight = Icons.Default.KeyboardArrowRight val AppIcon = R.drawable.ic_launcher_background val Policy = R.drawable.ic_policy_24dp } /** * A sealed class to make dealing with [ImageVector] and [DrawableRes] icons easier. */ sealed class DCodeIcon { data class ImageVectorIcon(val imageVector: ImageVector) : DCodeIcon() data class DrawableResourceIcon( @DrawableRes val id: Int) : DCodeIcon() } |
Step 6: Create another Kotlin data class FeatureList.kt
Kotlin
data class FeatureList( val name: String, val listIcon: DCodeIcon, val githubUrl: String, ) |
Step 7: Putting it all together
Kotlin
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Divider import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp const val username = "damahecode" const val my_description = "A group of simple, open source Android apps without ads and unnecessary permissions, with materials design UI." val profilePopularList = listOf( ProfilePopularList( "Jetpack-Compose-UI" , "A Collection on all Jetpack compose UI Layouts and Demo screens to see it's potential" , "25" , "Kotlin" ), ProfilePopularList( "Leaf-Explorer" , "File Manager, File Sharing & Music Player App for Android" , "9" , "Kotlin" ), ProfilePopularList( "DayNight-Theme" , "A Material Design-based Theme Management System for Android Jetpack Compose." , "45" , "Kotlin" ) ) val imageTextList = listOf( ImageTextList(ImageVectorIcon(MyIcons.Location), "Bharat/India" ), ImageTextList(ImageVectorIcon(MyIcons.Email), "damahecode@gmail.com" ), ImageTextList(ImageVectorIcon(MyIcons.AccountBox), "100 followers" ) ) val moreOptionsList = listOf( FeatureList( "Edit Profile" , ImageVectorIcon(MyIcons.Edit), "" ), FeatureList( "Manage Account" , ImageVectorIcon(MyIcons.AccountBox), "" ), FeatureList( "Privacy Policy" , DrawableResourceIcon(MyIcons.Policy), "" ), FeatureList( "About" , ImageVectorIcon(MyIcons.Info), "" ), FeatureList( "Help & Feedback" , DrawableResourceIcon(MyIcons.Android_Head), "" ), FeatureList( "Share 'Damahe Code'" , ImageVectorIcon(MyIcons.Share), "" ), ) @OptIn ( ExperimentalComposeUiApi:: class , ExperimentalMaterial3Api:: class ) @Composable fun ProfileScreen(onGoBack: () -> Unit) { Scaffold( modifier = Modifier.semantics { testTagsAsResourceId = true }, containerColor = Color.Transparent, contentColor = MaterialTheme.colorScheme.onBackground, topBar = { TopAppBar( title = { Text(text = stringResource(id = R.string.txt_profile)) }, navigationIcon = { IconButton(onClick = onGoBack) { Icon(MyIcons.ArrowBack, contentDescription = "Back" ) } }, actions = { IconButton(onClick = { }) { Icon(MyIcons.Search, contentDescription = "Search" ) } IconButton(onClick = { }) { Icon(MyIcons.MoreVert, contentDescription = "More" ) } }, colors = TopAppBarDefaults.centerAlignedTopAppBarColors( containerColor = Color.Transparent, ), ) } ) { padding -> ProfileContent( modifier = Modifier .verticalScroll(rememberScrollState()) .padding(padding) ) { TopProfileLayout() MainProfileContent() FooterContent() } } } @Composable fun ProfileContent( modifier: Modifier = Modifier, content: @Composable () -> Unit ) { Column(modifier) { content() } } @OptIn (ExperimentalLayoutApi:: class ) @Composable fun TopProfileLayout() { Surface( modifier = Modifier .fillMaxWidth() .padding( 12 .dp), shape = RoundedCornerShape( 8 ), ) { Column(modifier = Modifier.padding( 10 .dp)) { Row( modifier = Modifier.padding(vertical = 5 .dp), verticalAlignment = Alignment.CenterVertically, ) { Icon( painter = painterResource(id = DrawableResourceIcon(MyIcons.AppIcon).id), contentDescription = null , modifier = Modifier .clip(CircleShape) .size( 60 .dp) ) Column( modifier = Modifier .padding(horizontal = 8 .dp) .weight(1f) ) { Text( text = stringResource(id = R.string.app_name), style = MaterialTheme.typography.titleLarge ) Text( text = username, style = MaterialTheme.typography.labelMedium, overflow = TextOverflow.Ellipsis, ) } } Text( modifier = Modifier.padding(vertical = 5 .dp), text = my_description, style = MaterialTheme.typography.bodySmall, ) FlowRow(modifier = Modifier.padding(vertical = 5 .dp)) { imageTextList.forEach { ImageTextContent( modifier = Modifier.padding(vertical = 5 .dp), icon = { when (it.icon) { is ImageVectorIcon -> Icon( imageVector = it.icon.imageVector, contentDescription = null , modifier = Modifier .size( 20 .dp) ) is DrawableResourceIcon -> Icon( painter = painterResource(id = it.icon.id), contentDescription = null , modifier = Modifier .size( 20 .dp) ) } }, text = { Text( text = it.text, style = MaterialTheme.typography.labelLarge, ) } ) } } } } } @Composable fun ImageTextContent( icon: @Composable () -> Unit, text: @Composable () -> Unit, modifier: Modifier = Modifier ) { Row( modifier, verticalAlignment = Alignment.CenterVertically, ) { icon() Spacer(modifier = Modifier.width( 5 .dp)) text() Spacer(modifier = Modifier.width( 10 .dp)) } } @Composable fun MainProfileContent() { Surface( modifier = Modifier .fillMaxWidth() .padding( 12 .dp), shape = RoundedCornerShape( 8 ), ) { Column(modifier = Modifier.padding( 5 .dp)) { Text( modifier = Modifier .padding( 10 .dp), text = "Popular" , style = MaterialTheme.typography.titleMedium, ) PopularContentList() Divider(modifier = Modifier.padding(vertical = 15 .dp)) GitContentItem( modifier = Modifier.padding(vertical = 2 .dp), icon = { Icon( imageVector = ImageVectorIcon(MyIcons.List).imageVector, contentDescription = null , modifier = Modifier .size( 40 .dp) .padding( 6 .dp) ) }, text = { Text( text = "Repositories" , style = MaterialTheme.typography.labelLarge, ) }, endItem = { Text( modifier = Modifier.padding( 5 .dp), text = "24" ) } ) GitContentItem( modifier = Modifier.padding(vertical = 2 .dp), icon = { Icon( imageVector = ImageVectorIcon(MyIcons.Star).imageVector, contentDescription = null , modifier = Modifier .size( 40 .dp) .padding( 6 .dp) ) }, text = { Text( text = "Starred" , style = MaterialTheme.typography.labelLarge, ) }, endItem = { Text( modifier = Modifier.padding( 5 .dp), text = "60" ) } ) } } } @Composable fun PopularContentList() { LazyRow { items( items = profilePopularList, itemContent = { Surface( modifier = Modifier .width( 250 .dp) .padding( 5 .dp), shape = RoundedCornerShape( 8 ), border = BorderStroke( 0.1 .dp, MaterialTheme.colorScheme.outline) ) { Column(modifier = Modifier.padding( 5 .dp)) { Row( modifier = Modifier.padding(vertical = 5 .dp), verticalAlignment = Alignment.CenterVertically, ) { Icon( painter = painterResource(id = DrawableResourceIcon(MyIcons.AppIcon).id), contentDescription = null , modifier = Modifier .clip(CircleShape) .size( 20 .dp) ) Spacer(modifier = Modifier.width( 5 .dp)) Text( text = it.name, style = MaterialTheme.typography.titleSmall, ) } Text( modifier = Modifier.padding(vertical = 5 .dp), text = it.description, style = MaterialTheme.typography.bodySmall, maxLines = 2 , ) Row( modifier = Modifier.padding(vertical = 5 .dp), verticalAlignment = Alignment.CenterVertically, ) { ImageTextContent( modifier = Modifier.padding(vertical = 5 .dp), icon = { Icon( imageVector = ImageVectorIcon(MyIcons.Star).imageVector, contentDescription = null , modifier = Modifier .clip(CircleShape) .size( 15 .dp) ) }, text = { Text( text = it.star, style = MaterialTheme.typography.labelLarge, ) } ) Spacer(modifier = Modifier.width( 5 .dp)) ImageTextContent( modifier = Modifier.padding(vertical = 5 .dp), icon = { Icon( painter = painterResource(id = DrawableResourceIcon(MyIcons.AppIcon).id), contentDescription = null , modifier = Modifier .clip(CircleShape) .size( 10 .dp) ) }, text = { Text( text = it.language, style = MaterialTheme.typography.labelLarge, ) } ) } } } } ) } } @Composable fun GitContentItem( modifier: Modifier = Modifier, icon: @Composable () -> Unit, text: @Composable () -> Unit, endItem: @Composable () -> Unit, ) { Row( modifier, verticalAlignment = Alignment.CenterVertically, ) { icon() Column( modifier = Modifier .padding(horizontal = 5 .dp) .weight(1f) ) { text() } endItem() } } @Composable fun FooterContent() { Surface( modifier = Modifier .fillMaxWidth() .padding( 12 .dp), shape = RoundedCornerShape( 8 ), ) { Column(modifier = Modifier.padding( 5 .dp)) { Text( modifier = Modifier .padding( 10 .dp), text = stringResource(id = R.string.txt_more_options), style = MaterialTheme.typography.titleMedium, ) moreOptionsList.forEach { MoreOptionsComp(it) } } } } @Composable fun MoreOptionsComp( featureList: FeatureList, ) { Row( modifier = Modifier.padding( 5 .dp), verticalAlignment = Alignment.CenterVertically, ) { when (featureList.listIcon) { is ImageVectorIcon -> Icon( imageVector = featureList.listIcon.imageVector, contentDescription = null , modifier = Modifier .size( 40 .dp) .padding( 6 .dp) ) is DrawableResourceIcon -> Icon( painter = painterResource(id = featureList.listIcon.id), contentDescription = null , modifier = Modifier .size( 40 .dp) .padding( 6 .dp) ) } Column( modifier = Modifier .padding(horizontal = 4 .dp) .weight(1f) ) { Text( text = featureList.name, style = MaterialTheme.typography.labelLarge ) } Icon( imageVector = MyIcons.KeyboardArrowRight, contentDescription = null , modifier = Modifier.padding( 4 .dp) ) } } |
Now add fun ProfileScreen() that contains all the functions, in MainActivity as
Kotlin
import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super .onCreate(savedInstanceState) setContent { DCodeAppTheme { ProfileScreen( onGoBack = { } ) } } } } |
Finally, This code creates a User Profile UI that looks like the following:
Contact Us