1. Preface
As mobile application development continues to evolve, Google has introduced Jetpack Compose, a brand-new modern toolkit for building native Android UIs. Compose aims to simplify and accelerate the UI development process by using a declarative Kotlin DSL, making the construction of dynamic UIs simpler and more intuitive. We will delve into Jetpack Compose UI in three articles, with this one providing a detailed explanation of the basics required to go from zero to one in creating a Compose application, including foundational syntax, components (Composables), state management, layout system, framework, and development.
2. Basic Syntax
Jetpack Compose uses Kotlin's declarative syntax to build UIs. In a declarative UI, developers only need to describe the state that the interface should display, and the Compose framework takes care of rendering and automatically updating the UI when the state changes.
2.1. @Composable Annotation
In Compose, all UI components are defined by functions annotated with @Composable
. These functions can receive parameters, manage state, and can nest calls to other @Composable
functions.
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}
2.2. Composable Functions
Composable functions can call other composable functions just like regular functions, thus building complex UI hierarchies.
@Composable
fun UserProfile(name: String, age: Int) {
Column {
Greeting(name = name)
Text(text = "Age: $age")
}
}
2.3. Conditional Statements
Within composable functions, conditional statements can be used to control the visibility of UI elements.
@Composable
fun WelcomeMessage(isLoggedIn: Boolean) {
if (isLoggedIn) {
Text("Welcome back!")
} else {
Text("Please log in.")
}
}
2.4. Loops
Loops can be used to create repetitive UI elements, such as list items.
@Composable
fun NameList(names: List<String>) {
Column {
for (name in names) {
Text(name)
}
}
}
2.5. Higher-Order Functions and Lambda Expressions
Compose makes full use of Kotlin's higher-order functions and lambda expressions, making event handling and component reuse simple.
@Composable
fun ButtonWithClick(onClick: () -> Unit) {
Button(onClick = onClick) {
Text("Click me")
}
}
2.6. Modifiers
Modifiers are used to modify the layout parameters, appearance, and behavior of components. They can be chained together.
@Composable
fun StyledText(text: String) {
Text(
text = text,
modifier = Modifier
.padding(16.dp)
.background(Color.LightGray)
.padding(16.dp) // Add padding again to increase space
)
}
3. Components (Composables)
In Compose, the UI is built through a series of composable functions (Composables). These functions are marked with the @Composable
annotation and can contain other Composables, forming a tree-like structure. Each Composable function represents a part of the UI, such as a button, text, or list, and can receive parameters to customize its behavior and appearance.
3.1. Basic Components
Compose provides a range of basic components, such as Text
, Button
, Image
, etc., which are the building blocks for applications.
@Composable
fun BasicComponents() {
Text(text = "Hello, Compose!")
Button(onClick = { /* Handle click event */ }) {
Text("Click Me")
}
Image(painter = painterResource(id = R.drawable.ic_launcher), contentDescription = "Icon")
}
3.2. Custom Components
You can create custom components by combining existing Composables, which allows you to reuse UI code and keep your codebase clean.
@Composable
fun UserProfile(name: String, imageUrl: String) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(painter = rememberImagePainter(imageUrl), contentDescription = "User profile picture")
Text(text = name)
}
}
3.3. Component Reusability
Composables can be encapsulated and reused, making it simple to build consistent and maintainable UIs.
@Composable
fun ReusableListItem(title: String, subtitle: String) {
Column {
Text(text = title, style = MaterialTheme.typography.h6)
Text(text = subtitle, style = MaterialTheme.typography.subtitle1)
}
}
4. State Management
Compose manages UI state through observable state objects. When the state changes, Compose will re-invoke the relevant Composable functions to reflect the latest state. This mechanism ensures that the UI is always in sync with the data.
4.1. Observable State
Compose uses mutableStateOf
to create observable states. When the value of the state changes, all Composables that use that state will automatically update.
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count: $count")
}
}
4.2. State Hoisting
State hoisting is a pattern used to move state up to a higher position in the component tree to facilitate state management and sharing.
@Composable
fun CounterScreen() {
var count by remember { mutableStateOf(0) }
Counter(count, onCountChanged = { newCount -> count = newCount })
}
@Composable
fun Counter(count: Int, onCountChanged: (Int) -> Unit) {
Button(onClick = { onCountChanged(count + 1) }) {
Text("Count: $count")
}
}
4.3. State and Side Effects
Compose provides APIs like LaunchedEffect
and rememberCoroutineScope
to handle side effects, such as launching coroutines.
@Composable
fun Timer() {
val scope = rememberCoroutineScope()
var seconds by remember { mutableStateOf(0) }
LaunchedEffect(key1 = true) {
scope.launch {
while (isActive) {
delay(1000)
seconds++
}
}
}
Text("Timer: $seconds")
}
5. Layout System
Compose offers a flexible layout system that allows developers to combine and customize layouts in a declarative way. It includes a range of predefined layout Composables such as Row
, Column
, and Box
, as well as tools for creating custom layouts.
5.1. Layout Components
Compose provides layout components like Row
, Column
, and Box
to control the arrangement of child components.
@Composable
fun LayoutExample() {
Column {
Text("First item")
Row {
Text("Second item")
Text("Third item")
}
}
}
5.2. Modifiers
Modifiers are used to adjust layout parameters, add decoration, and handle user input. They can be chained together and customized.
@Composable
fun ModifierExample() {
Text(
"Hello, Compose!",
modifier = Modifier
.padding(16.dp)
.background(Color.LightGray)
.padding(16.dp)
)
}
5.3. Custom Layouts
While Compose provides many predefined layout components, sometimes you may need to create a custom layout. Compose allows you to do this through the Layout
function.
@Composable
fun CustomLayout(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// Logic for measuring and placing children
}
}
5.4. Themes and Styling
Compose allows you to define themes and styles to maintain consistency in your UI and supports dark mode and other configurations.
@Composable
fun ThemedScreen() {
MaterialTheme {
// Use colors and styles from the theme
Text("Hello, Compose!", style = MaterialTheme.typography.h1)
}
}
5.5. Previews
Using the @Preview
annotation, you can preview the UI of composable functions in Android Studio without having to run the app on a device.
@Preview
@Composable
fun PreviewGreeting() {
Greeting(name = "Android")
}
6. Framework
6.1. Accompanist
Accompanist is a collection of useful libraries that complement Jetpack Compose, providing functionalities not found in the core library, such as pagination, navigation, and animations.
6.2. MVIKotlin
MVIKotlin is a modern MVI framework for Kotlin that integrates seamlessly with Compose, offering a reactive way to handle the state of applications.
6.3. Dagger-Hilt
Dagger-Hilt is a dependency injection library that can be used in conjunction with Compose to simplify dependency management and communication between components.
7. Compose UI Development
In the AI era, development efficiency for Compose UI can also be improved through AI. For example, Codia AI Code supports converting designs into Compose UI Code.
7.1. Environment Setup
Make sure your Android Studio is up to date, as Jetpack Compose requires the latest tooling support. You can download the latest version of Android Studio from the Android Developer official website.
7.2. Creating a New Project
- Open Android Studio and click "Start a new Android Studio project."
- Select the "Empty Compose Activity" template.
- Enter your application name, package name, save location, and other information.
- Choose your target Android devices and the minimum API level (API 21 or higher is recommended).
- Click "Finish" to create the project.
7.3. Configuring Dependencies
In your project's build.gradle
file, make sure you have added the dependencies related to Compose and the Kotlin compiler plugin.
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
// ...
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.x.x' // Use the appropriate Compose version
}
}
dependencies {
implementation 'androidx.compose.ui:ui:1.x.x' // Compose UI library
// Other dependencies...
}
7.4. Building the UI
In the MainActivity.kt
file, you will find a default @Composable
function, which is the starting point for your application.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp {
Greeting("Android")
}
}
}
}
@Composable
fun MyApp(content: @Composable () -> Unit) {
// Theme settings for the application
MaterialTheme {
// Content of the application
content()
}
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
MyApp {
Greeting("Android")
}
}
7.5. Running the App
Click the "Run" button on the Android Studio toolbar and select a connected device or emulator to run your app. You should be able to see the text "Hello, Android!" displayed on the screen.
7.6. Adding Interactivity
Let's add a button and some state to make the app more interactive.
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Greeting(name = "Compose")
Button(onClick = { count++ }) {
Text("Click me!")
}
Text("Clicked $count times")
}
}
@Preview(showBackground = true)
@Composable
fun PreviewCounter() {
MyApp {
Counter()
}
}
Now, when you click the "Click me!" button, the counter's number will increase, and the UI will automatically update.
7.7. Styling and Layout
Use modifiers to add style and layout attributes.
@Composable
fun Counter() {
// ...
Button(
onClick = { count++ },
modifier = Modifier.padding(16.dp)
) {
Text("Click me!")
}
Text(
"Clicked $count times",
modifier = Modifier.padding(16.dp)
)
}
7.8. Testing and Debugging
Use Android Studio's Compose Preview feature to quickly see what your Composable functions look like. If you encounter issues, you can use the Layout Inspector and Logcat to debug.
7.9. Building More Complex UIs
As you become more familiar with Compose, you can start building more complex UIs, such as using LazyColumn
to create scrollable lists, or using Navigation
to add navigation between screens.