Jetpack Compose in Android
Declarative UI Design with Jetpack Compose
Every application has a UI framework behind it. These frameworks play a huge part in how the applications are created and their performance as well. They also have different ways of operation but can be summarized into two: declarative and imperative.
Imparative UI
This is the most common paradigm. It involves having a separate prototype/model of the application’s UI. This design focuses on the how rather than the what. A good example is XML layouts in Android. We design the widgets and components which are then rendered for the user to see and interact with.
Declarative UI
This pattern is an emerging trend that allows developers to design the user interface based on the data received. This on the other hand focuses on the what. This design paradigm makes use of one programming language to create an entire application.
Example of declarative UI framework
- Flutter
- React native
- SwiftUI
- Jetpack compose.
Getting Started with jetpack Compose
Jetpack compose is a modern toolkit for building a native Android user interface. It is simple and accelerated UI development on android with less code, powerful tools, and intuitive kotlin APIs.Ui components are built based on declarative functions.
Terms Used in Compose
Composable functions
Every element on an android UI created by jetpack compose is called composable. A composable is a normal kotlin function annotated with @Composable
annotation. This function does not have a return value. Its contents are added to the screen on rendering.
Layouts
Compose has three layouts to arrange its elements
- Using a Column
The Column function lets you arrange elements vertically. The main axis of a column is vertical therefore elements are arranged vertically example verticalArrangement = Arrangement.SpaceEvenly,
and alignment is done on the horizontal cross axis. example horizontalAlignment = Alignment.CenterHorizontally
.
- Using a Row
A Row function arranges items horizontally. The main axis of the row is horizontal therefore elements are arranged horizontally example horizontalArrangement = Arrangement.SpaceBetween
and alignment is done on the vertical cross axis. example verticalAlignment = Alignment.CenterVertically
.
- Box
Stack elements that arrange the elements on top of each other. Modifiers are used to design the box to users' needs.
List
Listing is done by using Compose’s LazyColumn
and LazyRow
. These composable render only the elements that are visible on screen, so they are designed to be very efficient for long lists
A LazyColumn is a vertically scrolling list that only composes and lays out the currently visible items. It’s similar to a Recyclerview in the classic Android View system.
A LazyRow is a horizontal scrolling list.
Modifiers
This object is used to define properties for our composable. It makes use of the builder pattern to set the properties. This means that changes are applied in the order in which they were set in the modifier object. This should be a great consideration when using the Modifier class.
To learn the jetpack compose concept we'll create a user interface similar to shopping Ui.
Step 1 - Creating a new Compose project
To begin a new project, go to File > New >New project, pick Empty to compose activity from the menu, and then hit Next. Give your project a descriptive name. Kotlin is selected automatically as the language and click Finish.
Step 2 - Understanding Compose Code Structure
The main activity is the entry point to an Android app. In our project, MainActivity is launched when the user opens the app (as it's specified in the AndroidManifest.xml file). You use setContent to define your layout, instead of using an XML file as you'd do in the traditional View system, you call Composable functions within it.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
Surface(color = MaterialTheme.colors.background) {
Greeting("Android")
}
}
}
}
}
This is a function annotated with @Composable
.This allows other composable functions to be called within it. For example, is Greeting function is marked as @Composable. This function will produce a piece of UI hierarchy displaying the given input, String. Text is a composable function provided by the library.
@Composable
private fun Greeting(name: String) {
Text(text = "Hello $name!")
}
To use the Android Studio preview, you just have to mark any parameterless Composable function or functions with default parameters with the @Preview annotation and build your project.
@Preview(showBackground = true, name = "Text preview")
@Composable
fun DefaultPreview() {
BasicsCodelabTheme {
Greeting(name = "Android")
}
}
Step 3- Creating Arranging composable
Following the shopping UI there are some composable we are going to create an example:-
- Image
- Text
- Card
- Icon
- Toolbar
- Button
To arrange the composable we shall consider using the layouts described earlier that is row column and box.
Lets start by creating a tool bar composable which have the text,navigation icon and action icon.The composable are created within a function annotated with @composable
.
@Composable
fun ToolBar() {
TopAppBar(
title = {
Text(
modifier = Modifier.fillMaxWidth(),
text = "Shopping Bag",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center
)
},
backgroundColor = Color.White,
//setting navigation icon
navigationIcon = {
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null)
},
//setting action icon
actions = {
Icon(imageVector = Icons.Default.ShoppingCart, contentDescription = null)
}
)
}
Next, let's set up a card item composable with several items. Where the items are arranged in columns and rows. The items are also given specific attributes using modifiers
@Composable
fun CartItem(
// declaring card item modifier
modifier: Modifier = Modifier
) {
//wraping composables in a card
Card(
modifier = modifier
.height(130.dp)
.padding(5.dp),
shape = RoundedCornerShape(15.dp),
elevation = 5.dp
)
{
//using row layout to arrange the elements
Row(
modifier = modifier.fillMaxWidth()
) {
Image(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth(0.25f)
.clip(shape = RoundedCornerShape(15.dp)),
contentScale = ContentScale.Crop,
painter = painterResource(R.drawable.pic),
contentDescription = null
)
Spacer(modifier = Modifier.width(8.dp))
Column(
verticalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth(0.9f),
) {
Text(
text = "Cotton Dress",
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold
)
Text(
text = "$250.00",
fontSize = 16.sp,
fontWeight = FontWeight.Bold
)
Row(
horizontalArrangement = Arrangement.SpaceEvenly
) {
Icon(
imageVector = Icons.Default.AddCircle,
contentDescription = null
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "1",
fontSize = 16.sp,
)
Spacer(modifier = Modifier.width(8.dp))
Icon(
imageVector = Icons.Default.AddCircle,
contentDescription = null
)
}
}
// using column layout to arrange the elements
Column(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth(),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "B", fontSize = 16.sp)
Icon(imageVector = Icons.Default.Delete, contentDescription = null)
}
}
}
Next, let's create a card with text and buttons arranged horizontally using a Row layout and giving it relevant spaces and properties.
@Composable
fun PromoCard(
modifier: Modifier = Modifier
) {
Card(
//setting card properties
shape = RoundedCornerShape(12.dp),
backgroundColor = Color.LightGray,
modifier = modifier
.fillMaxWidth()
.height(55.dp)
) {
// arranging elements in row
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier
.fillMaxWidth(0.4f)
.padding(start = 8.dp),
text = "Promo Code",
fontSize = 16.sp,
textAlign = TextAlign.Justify,
)
Button(
onClick = { /*TODO*/ },
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth(0.6f),
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.buttonColors(Orange)
) {
Text(text = "Apply", color = Color.White)
}
}
}
}
The card items initially created the need to be listed. To do listing we can use LazyColumn
or LazyRow
for our case the items need to be listed vertically and therefore we shall be using LazyColumn
and giving our lazy column with the item and specifying the count.
@Composable
fun CartList() {
LazyColumn {
items(3) {
CartItem(modifier = Modifier.fillMaxWidth())
}
}
}
Adding the remaining elements of the UI
@Composable
fun OtherDetails() {
Column(
modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier
.padding(start = 8.dp),
text = "Sub Total -------------------------------------------",
fontSize = 16.sp,
color = Color.Gray,
textAlign = TextAlign.Justify
)
Spacer(modifier = Modifier.width(8.dp))
Text(
modifier = Modifier
.padding(end = 16.dp),
text = "$250.00",
fontSize = 16.sp,
fontWeight = FontWeight.Bold
)
}
Spacer(modifier = Modifier.padding(16.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier
.padding(start = 8.dp),
text = "Shipping -------------------------------------------",
fontSize = 16.sp,
color = Color.Gray,
textAlign = TextAlign.Justify
)
Spacer(modifier = Modifier.width(8.dp))
Text(
modifier = Modifier
.padding(end = 16.dp),
text = "$4.00",
fontSize = 16.sp,
fontWeight = FontWeight.Bold
)
}
// composable to add space
Spacer(modifier = Modifier.padding(16.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier
.padding(start = 8.dp),
text = "Bag Total -------------------------------------------",
fontSize = 16.sp,
color = Color.Gray,
textAlign = TextAlign.Justify
)
// composable to add space
Spacer(modifier = Modifier.width(8.dp))
Text(
modifier = Modifier
.padding(end = 16.dp),
text = "$250.00",
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = Orange
)
}
// composable to add space
Spacer(modifier = Modifier.padding(16.dp))
Button(
onClick = { /*TODO*/ },
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.buttonColors(Orange)
) {
Text(text = "Proceed to checkout", color = Color.White, fontSize = 16.sp)
}
}
}
Finally, let us preview our UI, the UI can be either previewed in the dark or light mode. All the composable created are grouped within a column to give it a relevant layout on the screen.
@Preview(name = "Light Mode",showBackground = true,
)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
name = "Dark Mode"
)
@Composable
fun DefaultPreview() {
ShoppingTheme {
Column {
ToolBar()
CartList()
PromoCard(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
OtherDetails()
}
}
}
Conclusion
In this article, we have studied what is jetpack compose, the terms used in jetpack compose, and how to use jetpack compose in android. you can also learn more about composing side effects
Get the full code implementation on this GitHub Repository