Basic of Android Jetpack Compose
Jetpack Compose is a modern toolkit for building native Android UI.
Jetpack Compose is built around composable functions.
These functions let you define your app's UI programmatically by describing how it should look and providing data dependencies, rather than focusing on the process of the UI's construction.
To create a composable function, just add the @Composable annotation to the function name.
1.Composable functions
Add a text element
Define a content block and call the Text composable function.
The setContent block defines the activity's layout where composable functions are called.
Composable functions can only be called from other composable functions.
Define a composable function
To make a function composable, add the @Composable annotation.
Define a MessageCard function which is passed a name, and uses it to configure the text element.
Preview your function in Android Studio
The @Preview annotation lets you preview your composable functions within Android Studio without having to build and install the app to an Android device or emulator.
The annotation must be used on a composable function that does not take in parameters.
Fore this reason, you can't preview the MessageCard function directly.
Instead, make a second function named PreviewMessageCard, which calls MessageCard with an appropriate parameter.
The app itself doesn't change, since the new PreviewMessageCard function isn't called anywhere,
but Android Studio adds a preview window which you can expand by clicking on the split view.
This window shows a preview of the UI elements created by composable functions marked with the @Preview annotation.
My Android Studio is in dark theme mode. So, background of preview is a little bit black.
2. Layouts
Add multiple texts
UI elements are hierarchical, with elements contained in other elements.
Above code creates two text elements inside the content view.
However, since you haven't provided any information about how to arrange them, the text elements are drawn on top of each other, making text unreadable.
Using a Column
The Column function lets you arrange elements vertically.
You can use Row to arrange items horizontally and Box to stack elements.
Add an image element
Add a Row composable to have a well structured design and an Image composable inside it.
Configure your layout
To decorate or configure a composable, Compose uses modifiers.
They allow you to change the composable's size, layout, appearance or add high-level interactions, such as making an element clickable.
3. Material Design
Use Material Design
Compose is built to support Material Design principles. Many of its UI elements implement Material Design out of the box.
Jetpack Compose provides an implementation of Material Design 3 and its UI elements out of the box.
To start, wrap the MessageCard function with the Material theme created in your project, ComposeCodelabTheme, as well as a Surface.
Doing so will allow your composables to inherit styles as defined in your app's theme ensuring consistency across your app.
Material Design is built around three pillars: Color, Typography, and Shape
✋ Empty Compose Activity template generates a default theme for your project that allows you to customize MaterialTheme.
You can find your custom theme in the Theme.kt file.
Color
Use MaterialTheme.colorScheme to style with colors from the wrapped theme.
You can use these values from the theme anywhere a color is needed.
Typography
Material Typography styles are available in the MaterialTheme, just add them to the Text composables.
Shape
Wrap the message body text around a Surface composable.
Doing so allows customizing the message body's shape and elevation.
Padding is also added to the message for a better layout.
Enable dark theme
Dark theme can be enabled to avoid a bright display especially at night, or simply to save the device battery.
You can create multiple previews in your file as separate functions, or add multiple annotations to the same function.
4. Lists and animations
Create a list of messages
We use LazyColumn and LayRow to display several messages.
These composables render only the elements that are visible on screen, so they are designed to be very efficient for long lists.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeCodeLabTheme {
Conversation(SampleData.conversationSample)
}
}
}
}
data class Message(val author: String, val body: String)
@Composable
fun MessageCard(msg: Message) {
Row(modifier = Modifier.padding(all = 8.dp)) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "launcher foreground",
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
Column {
Text(text = msg.author,
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.titleSmall)
Spacer(modifier = Modifier.height(4.dp))
Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) {
Text(text = msg.body,
modifier = Modifier.padding(all = 4.dp),
style = MaterialTheme.typography.bodyMedium)
}
}
}
}
@Preview(name = "Light Mode")
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
ComposeCodeLabTheme {
Surface {
MessageCard(Message("Lexi", "Hey, take a look at Jetpack Compose"))
}
}
}
@Composable
fun Conversation(messages: List<Message>) {
LazyColumn {
items(messages) {message ->
MessageCard(message)
}
}
}
@Preview
@Composable
fun PreviewConversation() {
ComposeCodeLabTheme {
Conversation(SampleData.conversationSample)
}
}
object SampleData {
// Sample conversation data
val conversationSample = listOf(
Message(
"Lexi",
"Test...Test...Test..."
),
Message(
"Lexi",
"""List of Android versions:
|Android KitKat (API 19)
|Android Lollipop (API 21)
|Android Marshmallow (API 23)
|Android Nougat (API 24)
|Android Oreo (API 26)
|Android Pie (API 28)
|Android 10 (API 29)
|Android 11 (API 30)
|Android 12 (API 31)""".trim()
),
Message(
"Lexi",
"""I think Kotlin is my favorite programming language.
|It's so much fun!""".trim()
),
Message(
"Lexi",
"Searching for alternatives to XML layouts..."
),
Message(
"Lexi",
"""Hey, take a look at Jetpack Compose, it's great!
|It's the Android's modern toolkit for building native UI.
|It simplifies and accelerates UI development on Android.
|Less code, powerful tools, and intuitive Kotlin APIs :)""".trim()
),
Message(
"Lexi",
"It's available from API 21+ :)"
),
Message(
"Lexi",
"Writing Kotlin for UI seems so natural, Compose where have you been all my life?"
),
Message(
"Lexi",
"Android Studio next version's name is Arctic Fox"
),
Message(
"Lexi",
"Android Studio Arctic Fox tooling for Compose is top notch ^_^"
),
Message(
"Lexi",
"I didn't know you can now run the emulator directly from Android Studio"
),
Message(
"Lexi",
"Compose Previews are great to check quickly how a composable layout looks like"
),
Message(
"Lexi",
"Previews are also interactive after enabling the experimental setting"
),
Message(
"Lexi",
"Have you tried writing build.gradle with KTS?"
),
)
}
Animate messages while expanding
It's time to play with animations.
We need to keep track of local UI state whether a message has been expanded or not.
We have to use the functions remember and mutableStateOf.
Composable functions can store local state in memory by using remember, and track changes to the value passed to mutableStateOf.
Composables using this state will get redrawn automatically when the value is updated.
This is called recomposition.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeCodeLabTheme {
Conversation(SampleData.conversationSample)
}
}
}
}
data class Message(val author: String, val body: String)
@Composable
fun MessageCard(msg: Message) {
Row(modifier = Modifier.padding(all = 8.dp)) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "launcher foreground",
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
var isExpanded by remember { mutableStateOf(false) }
Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
Text(text = msg.author,
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.titleSmall)
Spacer(modifier = Modifier.height(4.dp))
Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) {
Text(text = msg.body,
modifier = Modifier.padding(all = 4.dp),
maxLines = if(isExpanded) Int.MAX_VALUE else 1,
style = MaterialTheme.typography.bodyMedium)
}
}
}
}
@Preview(name = "Light Mode")
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
ComposeCodeLabTheme {
Surface {
MessageCard(Message("Lexi", "Hey, take a look at Jetpack Compose"))
}
}
}
@Composable
fun Conversation(messages: List<Message>) {
LazyColumn {
items(messages) {message ->
MessageCard(message)
}
}
}
@Preview
@Composable
fun PreviewConversation() {
ComposeCodeLabTheme {
Conversation(SampleData.conversationSample)
}
}
You will use the clickable modifier to handle click events on the composable.
Instead of just toggling the background color of the Surface, you will animate the background color by gradually modifying its value from MaterialTheme.colorScheme.surface to MaterialTheme.colorScheme.primary and vice versa.
Lastly, you will use the animateContentSize modifier to animate the message container size smoothly.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeCodeLabTheme {
Conversation(SampleData.conversationSample)
}
}
}
}
data class Message(val author: String, val body: String)
@Composable
fun MessageCard(msg: Message) {
Row(modifier = Modifier.padding(all = 8.dp)) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "launcher foreground",
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
var isExpanded by remember { mutableStateOf(false) }
val surfaceColor by animateColorAsState(
if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface,
label = "",
)
Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
Text(text = msg.author,
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.titleSmall)
Spacer(modifier = Modifier.height(4.dp))
Surface(shape = MaterialTheme.shapes.medium,
shadowElevation = 1.dp,
color = surfaceColor,
modifier = Modifier.animateContentSize().padding(1.dp)
) {
Text(text = msg.body,
modifier = Modifier.padding(all = 4.dp),
maxLines = if(isExpanded) Int.MAX_VALUE else 1,
style = MaterialTheme.typography.bodyMedium)
}
}
}
}
@Preview(name = "Light Mode")
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
ComposeCodeLabTheme {
Surface {
MessageCard(Message("Lexi", "Hey, take a look at Jetpack Compose"))
}
}
}
@Composable
fun Conversation(messages: List<Message>) {
LazyColumn {
items(messages) {message ->
MessageCard(message)
}
}
}
@Preview
@Composable
fun PreviewConversation() {
ComposeCodeLabTheme {
Conversation(SampleData.conversationSample)
}
}
https://github.com/antwhale/ComposeCodeLab
👆You can find full code through above link