https://developer.android.com/courses/jetpack-compose/course
https://github.com/android/codelab-android-compose
https://developer.android.com/codelabs/jetpack-compose-basics
https://m3.material.io/get-started
https://github.com/philipplackner
https://www.youtube.com/playlist?list=PLQkwcJG4YTCSpJ2NLhDTHhi6XBNfk9WiC
Jetpack Compose is a modern toolkit designed to simplify UI development
It combines a reactive programming model with the conciseness and ease of use of the Kotlin programming language
It is fully declarative, meaning you describe your UI by calling a series of functions that transform data into a UI hierarchy
When the underlying data changes, the framework automatically re-executes these functions, updating the UI hierarchy for you
A Compose app is made up of composable functions - just regular functions marked with @Composable, which can call other composable functions
A function is all you need to create a new UI component
The annotation tells Compose to add special support to the function for updating and maintaining your UI over time
By making small reusable composables, it's easy to build up a library of UI elements used in your app
With Compose, an Activity remains 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, but instead of using an XML file as you'd do in the traditional View system, you call Composable functions within it
Let's create a simple Android Composable basic application:
See this youtube video: https://www.youtube.com/watch?v=xv2ZUuO3wwk&list=PLAzlSdU-KYwXYL1V9F-NkCy9MaNial-sm&index=5
class MainActivity: ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent{
Text("Hello World!")
}
}
}
The MainActivity class is the entry point of the application, and the onCreate method sets up the UI content using Compose's setContent function
We can now create out first Compose function
class MainActivity: ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent{
GreetingsMessage()
}
}
}
@Composable
fun GreetingsMessage(){
Text("Hello World!")
}
Now we can send a parameter in the function call
We can also create a Preview Composable to show the UI without running the application
package com.example.myfirstapp_text_only
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MensajeTarjeta("Daniel")
}
}
}
@Composable
fun MensajeTarjeta(name: String) {
Text(
text = "Hello $name!"
)
}
@Preview
@Composable
fun PreviewMensajeTarjeta() {
MensajeTarjeta("Daniel")
}
To visualize two texts one above the other we use the Column component
package com.example.myfirstapp_text_only
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Column {
NombreTarjeta("Daniel")
MensajeTarjeta()
}
}
}
}
@Composable
fun NombreTarjeta(name: String) {
Text(
text = "Hello $name!"
)
}
@Composable
fun MensajeTarjeta() {
Text(
text = "Hello esta es una descripcion con Compose"
)
}
package com.example.myfirstapp_text_only
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Text
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Column() {
Row {
Text(text = " Celda 1.1 ")
Text(text = " Celda 1.2 ")
Text(text = " Celda 1.3 ")
}
Row {
Text(text = " Celda 2.1 ")
Text(text = " Celda 2.2 ")
Text(text = " Celda 2.3 ")
}
Row {
Text(text = " Celda 3.1 ")
Text(text = " Celda 3.2 ")
Text(text = " Celda 3.3 ")
}
}
}
}
}
package com.example.myfirstapp_text_only
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Row() {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto"
)
Column() {
NombreTarjeta("Daniel")
MensajeTarjeta()
}
}
}
}
@Composable
fun NombreTarjeta(name: String) {
Text(
text = "Hello $name!"
)
}
@Composable
fun MensajeTarjeta() {
Text(
text = "Hello esta es una descripcion con Compose"
)
}
}
The first column will be the narrowest, the second will be wider, and the third will be the widest
To create three columns with different widths in a Jetpack Compose layout, you can use the Row composable along with Modifier.weight to distribute the available space among the columns
Each column can be given a different weight to control its width proportionally
Here's an example of how to achieve this:
package com.example.myfirstapp_text_only
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Column() {
Row() {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto"
)
}
Row() {
Column() {
ThreeColumns()
}
}
}
}
}
@Composable
fun ThreeColumns() {
Row(modifier = Modifier.fillMaxWidth()) {
// First column with weight 1 (smallest width)
Column(
modifier = Modifier
.weight(1f)
.padding(8.dp)
) {
Text("Col 1", style = MaterialTheme.typography.bodyLarge)
}
// Second column with weight 2 (medium width)
Column(
modifier = Modifier
.weight(2f)
.padding(8.dp)
) {
Text("Col 2", style = MaterialTheme.typography.bodyLarge)
}
// Third column with weight 3 (largest width)
Column(
modifier = Modifier
.weight(3f)
.padding(8.dp)
) {
Text("Col 3", style = MaterialTheme.typography.bodyLarge)
}
}
}
}
Explanation
Row(modifier = Modifier.fillMaxWidth()): Creates a horizontal layout that fills the available width of the parent
Column(modifier = Modifier.weight(xf).padding(8.dp)) { ... }:
Each Column is given a weight modifier. The weight determines how much of the available space each column should take relative to the others
The padding(8.dp) adds some spacing around the content of each column
Weights:
The first column has a weight of 1f, meaning it will take up one part of the available space
The second column has a weight of 2f, meaning it will take up two parts of the available space
The third column has a weight of 3f, meaning it will take up three parts of the available space
Text("Column X", style = MaterialTheme.typography.bodyLarge): Displays text in each column
You can replace this with any other composable content
How to change a Text Color
shadowElevation = 1.dp
modifier = Modifier.padding(all = 4.dp),
package com.example.myfirstapp_text_only
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Column {
LazyRow(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
item(1) {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier.padding(8.dp)
)
}
item(2) {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier.padding(8.dp)
)
}
item(3) {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier.padding(8.dp)
)
}
item(4) {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier.padding(8.dp)
)
}
item(5) {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier.padding(8.dp)
)
}
item(6) {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier.padding(8.dp)
)
}
item(7) {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier.padding(8.dp)
)
}
}
Row {
Column {
ThreeColumns()
}
}
}
}
}
@Composable
fun ThreeColumns() {
Row(modifier = Modifier.fillMaxWidth()) {
// First column with weight 1 (smallest width)
Column(
modifier = Modifier
.weight(1f)
.padding(8.dp)
) {
Text("Col 1", style = MaterialTheme.typography.bodyLarge)
}
// Second column with weight 2 (medium width)
Column(
modifier = Modifier
.weight(2f)
.padding(8.dp)
) {
Text("Col 2", style = MaterialTheme.typography.bodyLarge)
}
// Third column with weight 3 (largest width)
Column(
modifier = Modifier
.weight(3f)
.padding(8.dp)
) {
Text("Col 3", style = MaterialTheme.typography.bodyLarge)
}
}
}
}
package com.example.myfirstapp_text_only
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.NavHostController
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "main") {
composable("main") { MainScreen(navController) }
composable("new_screen1") { NewScreen1(navController) }
composable("new_screen2") { NewScreen2(navController) }
}
}
}
@Composable
fun MainScreen(navController: NavHostController) {
Column {
LazyRow(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
item {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier
.padding(8.dp)
.clickable {
navController.navigate("new_screen1")
}
)
}
item {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier
.padding(8.dp)
.clickable {
navController.navigate("new_screen2")
}
)
}
item {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier.padding(8.dp)
)
}
item {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier.padding(8.dp)
)
}
item {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier.padding(8.dp)
)
}
item {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier.padding(8.dp)
)
}
item {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier.padding(8.dp)
)
}
}
Row {
Column {
ThreeColumns()
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NewScreen1(navController: NavHostController) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("New Screen1") },
navigationIcon = {
IconButton(onClick = { navController.navigateUp() }) {
Icon(painter = painterResource(id = android.R.drawable.ic_menu_revert), contentDescription = "Back")
}
}
)
},
content = { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(16.dp)
) {
Text(text = "This is New Screen1", style = MaterialTheme.typography.bodyLarge)
}
}
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NewScreen2(navController: NavHostController) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("New Screen2") },
navigationIcon = {
IconButton(onClick = { navController.navigateUp() }) {
Icon(painter = painterResource(id = android.R.drawable.ic_menu_revert), contentDescription = "Back")
}
}
)
},
content = { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(16.dp)
) {
Text(text = "This is New Screen2", style = MaterialTheme.typography.bodyLarge)
}
}
)
}
@Composable
fun ThreeColumns() {
Row(modifier = Modifier.fillMaxWidth()) {
// First column with weight 1 (smallest width)
Column(
modifier = Modifier
.weight(1f)
.padding(8.dp)
) {
Text("Col 1", style = MaterialTheme.typography.bodyLarge)
}
// Second column with weight 2 (medium width)
Column(
modifier = Modifier
.weight(2f)
.padding(8.dp)
) {
Text("Col 2", style = MaterialTheme.typography.bodyLarge)
}
// Third column with weight 3 (largest width)
Column(
modifier = Modifier
.weight(3f)
.padding(8.dp)
) {
Text("Col 3", style = MaterialTheme.typography.bodyLarge)
}
}
}
}
When we click on the first Burger image or in the second we navigate to another screen
This is the Screen 1. We navigate to this screen when we click on the first burger image
1.13. We also add three Tabs in the HomeScreen, a Top Navigation Bar, a Bottom Navigation bar and a DropDown Menu
package com.example.myfirstapp_text_only
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.NavHostController
import com.google.accompanist.pager.*
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
Scaffold(
topBar = { TopBar(navController) },
bottomBar = { BottomNavigationBar(navController) }
) { innerPadding ->
NavHost(navController = navController, startDestination = "main", modifier = Modifier.padding(innerPadding)) {
composable("main") { MainScreen(navController) }
composable("new_screen1") { NewScreen1(navController) }
composable("new_screen2") { NewScreen2(navController) }
}
}
}
}
@Composable
fun TopBar(navController: NavHostController) {
var expanded by remember { mutableStateOf(false) }
TopAppBar(
title = { Text("My App") },
actions = {
IconButton(onClick = { expanded = true }) {
Icon(painter = painterResource(id = android.R.drawable.ic_menu_more), contentDescription = "More")
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
DropdownMenuItem(onClick = {
navController.navigate("main")
expanded = false
}) {
Text("Main")
}
DropdownMenuItem(onClick = {
navController.navigate("new_screen1")
expanded = false
}) {
Text("Screen 1")
}
DropdownMenuItem(onClick = {
navController.navigate("new_screen2")
expanded = false
}) {
Text("Screen 2")
}
}
}
)
}
@Composable
fun BottomNavigationBar(navController: NavHostController) {
BottomNavigation {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
BottomNavigationItem(
icon = { Icon(painter = painterResource(id = android.R.drawable.ic_menu_view), contentDescription = "Main") },
selected = currentRoute == "main",
onClick = {
navController.navigate("main") {
popUpTo(navController.graph.startDestinationId) { saveState = true }
launchSingleTop = true
restoreState = true
}
},
label = { Text("Main") }
)
BottomNavigationItem(
icon = { Icon(painter = painterResource(id = android.R.drawable.ic_menu_add), contentDescription = "Screen 1") },
selected = currentRoute == "new_screen1",
onClick = {
navController.navigate("new_screen1") {
popUpTo(navController.graph.startDestinationId) { saveState = true }
launchSingleTop = true
restoreState = true
}
},
label = { Text("Screen 1") }
)
BottomNavigationItem(
icon = { Icon(painter = painterResource(id = android.R.drawable.ic_menu_agenda), contentDescription = "Screen 2") },
selected = currentRoute == "new_screen2",
onClick = {
navController.navigate("new_screen2") {
popUpTo(navController.graph.startDestinationId) { saveState = true }
launchSingleTop = true
restoreState = true
}
},
label = { Text("Screen 2") }
)
}
}
@OptIn(ExperimentalPagerApi::class)
@Composable
fun MainScreen(navController: NavHostController) {
val tabs = listOf("Tab 1", "Tab 2", "Tab 3", "Tab 4")
val pagerState = rememberPagerState()
val coroutineScope = rememberCoroutineScope()
Column {
// LazyRow with images
LazyRow(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
item {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier
.padding(8.dp)
.clickable {
navController.navigate("new_screen1")
}
)
}
item {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier
.padding(8.dp)
.clickable {
navController.navigate("new_screen2")
}
)
}
item {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier.padding(8.dp)
)
}
item {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier.padding(8.dp)
)
}
item {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier.padding(8.dp)
)
}
item {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier.padding(8.dp)
)
}
item {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier.padding(8.dp)
)
}
}
// Tabs and HorizontalPager
TabRow(selectedTabIndex = pagerState.currentPage) {
tabs.forEachIndexed { index, title ->
Tab(
selected = pagerState.currentPage == index,
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
},
text = { Text(title) }
)
}
}
HorizontalPager(state = pagerState, count = tabs.size) { page ->
when (page) {
0 -> Tab1Screen(navController)
1 -> Tab2Screen()
2 -> Tab3Screen()
3 -> Tab4Screen()
}
}
// ThreeColumns content
Row {
Column {
ThreeColumns()
}
}
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun NewScreen1(navController: NavHostController) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("New Screen1") },
navigationIcon = {
IconButton(onClick = { navController.navigateUp() }) {
Icon(painter = painterResource(id = android.R.drawable.ic_menu_revert), contentDescription = "Back")
}
}
)
},
content = { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(16.dp)
) {
Text(text = "This is New Screen1", style = MaterialTheme.typography.h4)
}
}
)
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun NewScreen2(navController: NavHostController) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("New Screen2") },
navigationIcon = {
IconButton(onClick = { navController.navigateUp() }) {
Icon(painter = painterResource(id = android.R.drawable.ic_menu_revert), contentDescription = "Back")
}
}
)
},
content = { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(16.dp)
) {
Text(text = "This is New Screen2", style = MaterialTheme.typography.h4)
}
}
)
}
@Composable
fun Tab1Screen(navController: NavHostController) {
Column {
LazyRow(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
item {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier
.padding(8.dp)
.clickable {
navController.navigate("new_screen1")
}
)
}
item {
Image(
painter = painterResource(id = R.drawable._d_burger),
contentDescription = "Foto",
modifier = Modifier
.padding(8.dp)
.clickable {
navController.navigate("new_screen2")
}
)
}
// Add more items as needed
}
Row {
Column {
ThreeColumns()
}
}
}
}
@Composable
fun Tab2Screen() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text("This is Tab 2", style = MaterialTheme.typography.h4)
}
}
@Composable
fun Tab3Screen() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text("This is Tab 3", style = MaterialTheme.typography.h4)
}
}
@Composable
fun Tab4Screen() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text("This is Tab 4", style = MaterialTheme.typography.h4)
}
}
@Composable
fun ThreeColumns() {
Row(modifier = Modifier.fillMaxWidth()) {
// First column with weight 1 (smallest width)
Column(
modifier = Modifier
.weight(1f)
.padding(8.dp)
) {
Text("Col 1", style = MaterialTheme.typography.subtitle1)
}
// Second column with weight 2 (medium width)
Column(
modifier = Modifier
.weight(2f)
.padding(8.dp)
) {
Text("Col 2", style = MaterialTheme.typography.subtitle1)
}
// Third column with weight 3 (largest width)
Column(
modifier = Modifier
.weight(3f)
.padding(8.dp)
) {
Text("Col 3", style = MaterialTheme.typography.subtitle1)
}
}
}
}
To start a new Compose project, open Android Studio
Select File > New > New Project from the menu bar
For a new project, choose Empty Activity from the available templates
Click Next and configure your project
When choosing the Empty Activity template, the following code is generated for you in your project:
The project is already configured to use Compose
The AndroidManifest.xml file is created
The build.gradle.kts and app/build.gradle.kts files contain options and dependencies needed for Compose
After syncing the project, open MainActivity.kt and check out the code
MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
BasicsCodelabTheme {
Greeting("Android")
}
}
https://developer.android.com/static/codelabs/jetpack-compose-basics/img/8d24a786bfe1a8f2.gif
Now compare your code with the solution:
import androidx.compose.foundation.layout.fillMaxWidth
@Composable
fun MyApp(
modifier: Modifier = Modifier,
names: List<String> = listOf("World", "Compose")
) {
Column(modifier = modifier.padding(vertical = 4.dp)) {
for (name in names) {
Greeting(name = name)
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Column(modifier = Modifier.fillMaxWidth().padding(24.dp)) {
Text(text = "Hello ")
Text(text = name)
}
}
}
In the next step you'll add a clickable buttont that expands the Greeting Card
The goal is to create the following layout:
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.ElevatedButton
// ...
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(modifier = Modifier.weight(1f)) {
Text(text = "Hello ")
Text(text = name)
}
ElevatedButton(
onClick = { /* TODO */ }
) {
Text("Show more")
}
}
}
}
To add internal state to a composable, you can use the mutableStateOf function, which makes Compose recompose functions that read that State
State and MutableState are interfaces that hold some value and trigger UI updates (recompositions) whenever that value changes
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
val expanded = remember { mutableStateOf(false) }
val extraPadding = if (expanded.value) 48.dp else 0.dp
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(
modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding)
) {
Text(text = "Hello ")
Text(text = name)
}
ElevatedButton(
onClick = { expanded.value = !expanded.value }
) {
Text(if (expanded.value) "Show less" else "Show more")
}
}
}
}
This code is a simple Android application built using Jetpack Compose, which is a modern toolkit for building native Android UI
The application displays a greeting card with expandable content
Here’s a step-by-step explanation of the code:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
MyApp(modifier = Modifier.fillMaxSize())
}
}
}
}
MainActivity extends ComponentActivity and overrides the onCreate method
In onCreate, it calls setContent to set the layout using Compose
The layout is wrapped in a custom theme (BasicsCodelabTheme) and calls the MyApp composable function
@Composable
fun MyApp(modifier: Modifier = Modifier) {
var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) }
Surface(modifier) {
if (shouldShowOnboarding) {
OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
} else {
Greetings()
}
}
}
MyApp is the main composable function
It uses a state variable shouldShowOnboarding to track whether the onboarding screen should be shown
If shouldShowOnboarding is true, it shows OnboardingScreen. Otherwise, it shows Greetings
@Composable
fun OnboardingScreen(
onContinueClicked: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Welcome to the Basics Codelab!")
Button(
modifier = Modifier.padding(vertical = 24.dp),
onClick = onContinueClicked
) {
Text("Continue")
}
}
}
OnboardingScreen displays a welcome message and a "Continue" button
When the button is clicked, it calls onContinueClicked, which changes the state in MyApp to show the greetings
@Composable
private fun Greetings(
modifier: Modifier = Modifier,
names: List<String> = List(25) { "$it" }
) {
LazyColumn(modifier = modifier.padding(vertical = 4.dp)) {
items(items = names) { name ->
Greeting(name = name)
}
}
}
Greetings displays a list of greeting cards using LazyColumn
Each card is created by the Greeting composable
@Composable
private fun Greeting(name: String, modifier: Modifier = Modifier) {
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.primary
),
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
CardContent(name)
}
}
Greeting creates a card for each name
The card's content is defined by the CardContent composable
@Composable
private fun CardContent(name: String) {
var expanded by rememberSaveable { mutableStateOf(false) }
Row(
modifier = Modifier
.padding(12.dp)
.animateContentSize(
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
) {
Column(
modifier = Modifier
.weight(1f)
.padding(12.dp)
) {
Text(text = "Hello, ")
Text(
text = name, style = MaterialTheme.typography.headlineMedium.copy(
fontWeight = FontWeight.ExtraBold
)
)
if (expanded) {
Text(
text = ("Composem ipsum color sit lazy, " +
"padding theme elit, sed do bouncy. ").repeat(4),
)
}
}
IconButton(onClick = { expanded = !expanded }) {
Icon(
imageVector = if (expanded) Filled.ExpandLess else Filled.ExpandMore,
contentDescription = if (expanded) {
stringResource(R.string.show_less)
} else {
stringResource(R.string.show_more)
}
)
}
}
}
CardContent contains the actual content of the card
It includes a Row with a Text element to greet the user and another Text element to display the name
The card can expand and collapse to show more content. The expansion state is managed by the expanded variable
The IconButton changes the expansion state and updates the icon accordingly
These functions allow you to see how the UI components will look without running the app:
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
BasicsCodelabTheme {
OnboardingScreen(onContinueClicked = {}) // Do nothing on click.
}
}
@Preview(
showBackground = true,
widthDp = 320,
uiMode = UI_MODE_NIGHT_YES,
name = "GreetingPreviewDark"
)
@Preview(showBackground = true, widthDp = 320)
@Composable
fun GreetingPreview() {
BasicsCodelabTheme {
Greetings()
}
}
@Preview
@Composable
fun MyAppPreview() {
BasicsCodelabTheme {
MyApp(Modifier.fillMaxSize())
}
}