Android development has evolved significantly over the years, and one of the most exciting advancements is Jetpack Compose - Google's modern declarative UI toolkit for Android. If you've ever struggled with the complexity of XML layouts, understanding ViewGroups, or managing UI state with traditional Android views, Jetpack Compose offers a refreshing alternative. This tutorial will guide you through building your first Android app using Jetpack Compose, explaining core concepts while providing practical examples to solidify your understanding.
Setting Up Your Development Environment
Before diving into code, let's set up your development environment to work effectively with Jetpack Compose.
Installing Android Studio
First, you'll need the latest version of Android Studio. Jetpack Compose requires Android Studio Arctic Fox (2020.3.1) or newer:
- Visit the
Android Studio download page
- Download and install the appropriate version for your operating system
- Follow the installation wizard, ensuring you select the Android SDK components
Creating a New Jetpack Compose Project
Once Android Studio is installed, follow these steps to create a new Compose project:
- Open Android Studio and select "New Project"
- In the templates gallery, select "Empty Compose Activity"
- Name your project "ComposeFirstApp" (or any name you prefer)
- Set the minimum SDK to at least API level 21 (Android 5.0), though API level 24 or higher is recommended for all Compose features
- Click "Finish" to create your project
Understanding the Project Structure
Android Studio will generate several key files for your project:
MainActivity.kt
: The main entry point for your app that hosts your Compose UIui/theme/
: Directory containing theme-related files for colors, typography, and shapesbuild.gradle
(app level): Contains dependencies for Jetpack Compose and other librariesAndroidManifest.xml
: Defines app permissions and configurations
Note the Compose dependencies in your app-level
build.gradle
file:1dependencies {
2 implementation "androidx.compose.ui:ui:$compose_version"
3 implementation "androidx.compose.material:material:$compose_version"
4 implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
5 // ...
6}
7
Your First Composable Function: "Hello, World!"
Jetpack Compose is built around composable functions, which are Kotlin functions annotated with
@Composable
. These functions describe what your UI should look like, and the Compose runtime takes care of the rest.Introduction to Composable Functions
In Jetpack Compose, UI elements are represented by composable functions. Let's look at your first composable in
MainActivity.kt
:1class MainActivity : ComponentActivity() {
2 override fun onCreate(savedInstanceState: Bundle?) {
3 super.onCreate(savedInstanceState)
4 setContent {
5 ComposeFirstAppTheme {
6 // A surface container using the 'background' color from the theme
7 Surface(color = MaterialTheme.colors.background) {
8 Greeting("Android")
9 }
10 }
11 }
12 }
13}
14
15@Composable
16fun Greeting(name: String) {
17 Text(text = "Hello $name!")
18}
19
The
@Composable
annotation tells the Compose compiler that this function is designed to convert data into UI. The setContent
block defines the activity's layout using composable functions instead of setting a layout resource.Displaying Text with the Text
Composable
Let's modify the
Greeting
function to create a more interesting "Hello, World!" example:1@Composable
2fun Greeting(name: String) {
3 Text(
4 text = "Hello $name!",
5 fontSize = 24.sp,
6 fontWeight = FontWeight.Bold,
7 color = MaterialTheme.colors.primary
8 )
9}
10
Previewing Your Composable
One of the powerful features of Jetpack Compose is the ability to preview your UI directly in Android Studio using the
@Preview
annotation. Add this function to see a preview:1@Preview(showBackground = true)
2@Composable
3fun DefaultPreview() {
4 ComposeFirstAppTheme {
5 Greeting("Compose")
6 }
7}
8
Click the split-view button in the top-right corner of your editor to see the preview alongside your code. Any changes you make to the
Greeting
function will be reflected in the preview.Building a Simple UI: The Bullseye Game
Now let's build a simple game called "Bullseye" where players try to position a slider as close as possible to a randomly generated target value.
Creating the Basic Layout with Column
and Row
Jetpack Compose provides layout components like
Column
and Row
for arranging UI elements vertically and horizontally:1@Composable
2fun BullseyeGame() {
3 Column(
4 horizontalAlignment = Alignment.CenterHorizontally,
5 modifier = Modifier
6 .fillMaxSize()
7 .padding(16.dp)
8 ) {
9 Text(
10 text = "🎯 Bullseye 🎯",
11 style = MaterialTheme.typography.h4,
12 fontWeight = FontWeight.Bold
13 )
14
15 Spacer(modifier = Modifier.height(32.dp))
16
17 Text(
18 text = "Move the slider as close as you can to: 50",
19 style = MaterialTheme.typography.subtitle1
20 )
21
22 Spacer(modifier = Modifier.height(24.dp))
23
24 // Slider will go here
25
26 Spacer(modifier = Modifier.height(24.dp))
27
28 // Button will go here
29
30 Spacer(modifier = Modifier.height(32.dp))
31
32 Text(
33 text = "Score: 0",
34 style = MaterialTheme.typography.h5
35 )
36 }
37}
38
The
Modifier
is a key concept in Compose. It allows you to change the appearance and behavior of composables. In the code above, we use modifiers to fill the screen, add padding, and set spacing between elements.Adding a Slider for User Input
Now let's add a
Slider
component for user input:1// Inside the BullseyeGame composable, replace "// Slider will go here" with:
2Slider(
3 value = 50f, // Default starting value
4 onValueChange = { /* We'll implement this later */ },
5 valueRange = 1f..100f,
6 steps = 0, // Continuous slider
7 modifier = Modifier.padding(horizontal = 16.dp)
8)
9
10Text(
11 text = "50", // Display the current slider value
12 style = MaterialTheme.typography.body1
13)
14
Styling Your UI with MaterialTheme
Jetpack Compose comes with built-in Material Design components. The
MaterialTheme
provides consistent styling across your app. We've already used some of its properties in our code:MaterialTheme.colors.primary
- The primary color from your themeMaterialTheme.typography.h4
- Typography for headlines
You can customize the theme in the
ui/theme/
directory. For example, to change the primary color, modify the Theme.kt
file.Making Your App Interactive: Adding State
UI state is a crucial concept in Jetpack Compose. It determines what is displayed on screen and how the app responds to user interactions.
Understanding State in Jetpack Compose
In Compose, state is any value that can change over time. When state changes, the UI is automatically updated to reflect those changes - this is the "reactive" part of Compose.
Using remember
and mutableStateOf
Let's add state to our Bullseye game:
1@Composable
2fun BullseyeGame() {
3 // State variables
4 val targetValue = remember { mutableStateOf(Random.nextInt(1, 101)) }
5 val sliderPosition = remember { mutableStateOf(50f) }
6 val score = remember { mutableStateOf(0) }
7
8 Column(
9 horizontalAlignment = Alignment.CenterHorizontally,
10 modifier = Modifier
11 .fillMaxSize()
12 .padding(16.dp)
13 ) {
14 Text(
15 text = "🎯 Bullseye 🎯",
16 style = MaterialTheme.typography.h4,
17 fontWeight = FontWeight.Bold
18 )
19
20 Spacer(modifier = Modifier.height(32.dp))
21
22 Text(
23 text = "Move the slider as close as you can to: ${targetValue.value}",
24 style = MaterialTheme.typography.subtitle1
25 )
26
27 Spacer(modifier = Modifier.height(24.dp))
28
29 Slider(
30 value = sliderPosition.value,
31 onValueChange = { sliderPosition.value = it },
32 valueRange = 1f..100f,
33 steps = 0,
34 modifier = Modifier.padding(horizontal = 16.dp)
35 )
36
37 Text(
38 text = sliderPosition.value.roundToInt().toString(),
39 style = MaterialTheme.typography.body1
40 )
41
42 // Button will go here
43
44 Spacer(modifier = Modifier.height(32.dp))
45
46 Text(
47 text = "Score: ${score.value}",
48 style = MaterialTheme.typography.h5
49 )
50 }
51}
52
The
remember
function preserves state across recompositions, and mutableStateOf
creates an observable state object that triggers UI updates when changed.Updating UI Based on State Changes
Notice how we connect the state to the UI:
sliderPosition.value
is used as the current value of the SlideronValueChange = { sliderPosition.value = it }
updates the state when the slider moves- The Text below the slider displays
sliderPosition.value
formatted as an integer
Implementing Game Logic and Adding Interactivity
Now let's add the button and game logic:
1// Inside the BullseyeGame composable, replace "// Button will go here" with:
2Spacer(modifier = Modifier.height(24.dp))
3
4Button(
5 onClick = {
6 // Calculate score based on how close the slider is to the target
7 val sliderRounded = sliderPosition.value.roundToInt()
8 val difference = abs(targetValue.value - sliderRounded)
9
10 // Scoring logic
11 val pointsEarned = when {
12 difference == 0 -> 100 // Perfect hit
13 difference <= 5 -> 50 // Close
14 difference <= 10 -> 20 // Not bad
15 else -> 10 // Try harder
16 }
17
18 // Update score
19 score.value += pointsEarned
20
21 // Generate new target value
22 targetValue.value = Random.nextInt(1, 101)
23
24 // Reset slider position
25 sliderPosition.value = 50f
26 },
27 colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.primary)
28) {
29 Text(
30 text = "Hit Me!",
31 color = Color.White,
32 style = MaterialTheme.typography.button
33 )
34}
35
Don't forget to add the necessary imports at the top of the file:
1import androidx.compose.foundation.layout.*
2import androidx.compose.material.*
3import androidx.compose.runtime.*
4import androidx.compose.ui.Alignment
5import androidx.compose.ui.Modifier
6import androidx.compose.ui.graphics.Color
7import androidx.compose.ui.text.font.FontWeight
8import androidx.compose.ui.unit.dp
9import kotlin.math.abs
10import kotlin.math.roundToInt
11import kotlin.random.Random
12
Finally, update the
MainActivity.kt
to use our new game:1class MainActivity : ComponentActivity() {
2 override fun onCreate(savedInstanceState: Bundle?) {
3 super.onCreate(savedInstanceState)
4 setContent {
5 ComposeFirstAppTheme {
6 Surface(color = MaterialTheme.colors.background) {
7 BullseyeGame()
8 }
9 }
10 }
11 }
12}
13
Key Takeaways
Throughout this tutorial, you've learned several fundamental Jetpack Compose concepts:
- Composable Functions: UI elements are created using
@Composable
functions - Layout Components:
Column
andRow
are used for arranging UI elements - State Management:
remember
andmutableStateOf
manage UI state - Modifiers: Customize appearance and behavior of composables
- Material Design: Built-in implementation of Google's design system
The simplicity and power of Jetpack Compose are evident even in this small example. Compared to traditional Android UI development, you've written significantly less code and created a more maintainable architecture.
Conclusion
Congratulations! You've built your first Android app using Jetpack Compose. This modern UI toolkit makes Android development more efficient and enjoyable by providing a declarative approach to building user interfaces.
As you continue your Jetpack Compose journey, experiment with different composables, layouts, and more complex state management. The official
Jetpack Compose documentation
is an excellent resource for further exploration.What will you build next with Jetpack Compose? Share your first Compose app with us in the comments below!
Want to level-up your learning? Subscribe now
Subscribe to our newsletter for more tech based insights
FAQ