Building UI with Jetpack Compose. Part 1: Bottom Sheet Implementation
--
This is the first part of a multi-part series about my experience with Jetpack Compose. I wanted to share how easy it is to build Android apps with this new UI toolkit from Google. In this series, we will take a closer look at the simple weather app I’ve built for AndroidDevChallenge.
So what exactly is Jetpack Compose?
It’s a new UI toolkit that uses a declarative approach to programming. No more XML files, you can just describe your UI with composable functions that take in data and emit UI elements. Every time the app state changes UI updates automatically. So you can focus on building beautiful and complex design with much less code and using powerful tools. Have you already seen a Compose take on RecyclerView? Say we want to create a simple list of plants.
@Composable
fun PlantsList(plants: List<String>) {
LazyColumn() {
items(items = plants) { plant ->
Text(plant, modifier = Modifier.padding(24.dp))
}
}
}
And that’s it, our scrollable list is ready!
So no adapters, ViewHolders and XML anymore? Sounds like a good enough reason to start learning Compose. If you are not sure where to start, I recommend to check out this official Compose pathway.
Now, let’s dive into the Weather app example.
The idea behind this app was to create a simple and clear design, but with all the essential information about the weather conditions. I wanted to keep the more detailed weather forecast easily accessible for the user, but not fully visible on the main screen. For this purpose, I decided to implement BottomSheetScaffold from Experimental Material API.
BottomSheetScaffold implementation
These code samples are based on Compose 1.0.0-beta03. The API methods might change in the near future.
Step 1: Create a new project
So let’s start the implementation!
In order to use Jetpack Compose you have to install the latest Canary version of Android Studio. Here is a setup guide from Google documentation.
When it’s done, create a new Empty Compose Activity.
Step 2: Setup the project
First, let’s delete the default Greeting Composable from MainActivity. Then create a new composable function called WeatherApp.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
Surface(color = MaterialTheme.colors.background) {
WeatherApp()
}
}
}
}
@Composable
fun WeatherApp() { }
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
MyApplicationTheme {
WeatherApp()
}
}
}
Step 3: Create Bottom Sheet
Since BottomSheetScaffold is a part of the experimental API, we need to add @ExperimentalMaterialApi annotation to be able to use it. Let's add it before our MainActivity class.
@ExperimentalMaterialApi
class MainActivity : ComponentActivity() {
Now we are ready to implement BottomSheetScaffold Composable inside our WeatherApp function.
@Composable
fun WeatherApp() {
val scaffoldState = rememberBottomSheetScaffoldState()
BottomSheetScaffold(
sheetContent = {
//Weather forecast
},
scaffoldState = scaffoldState
) { innerPadding ->
//Image or animation content
}
}
We define scaffoldState so that BottomSheetScaffold Composable can handle the changes of app state. You can read more about the State in this guide.
If we run the app we can see that our BottomSheet is already here:
But it’s completely useless without content.
Step 4: Adding some weather data
To not overcomplicate things let’s begin with the simplest layout with a current temperature, city and forecast for the next week. We would need a list of data to populate our LazyColumn.
val weatherData = listOf("Tuesday 10°", "Wednesday 11°", "Thursday 12°", "Friday 13°","Saturday 14°", "Sunday 15°", "Monday 16°"
)
And now we can put it all together inside the sheetContent, using Column as a container to place items vertically.
sheetContent = {
Column() {
Text("12°")
Text("New York")
LazyColumn() {
items(items = weatherData){ weather ->
Text(weather)
}
}
}
},
To make it look a bit better we should use modifier elements. They decorate or add behavior to an elements of Compose Layout. For example, padding or background color of the text.
Column(
modifier = Modifier.padding(horizontal = 16.dp)
) {
Text(
"12°",
fontSize = 96.sp
)
Text(
"New York",
Modifier.padding(horizontal = 12.dp),
fontSize = 24.sp
)
Spacer(modifier = Modifier.height(12.dp))
LazyColumn() {
items(items = weatherData){ weather ->
Text(
weather,
Modifier.padding(
vertical = 4.dp,
horizontal = 12.dp
),
fontSize = 16.sp
)
}
}
Spacer(modifier = Modifier.height(12.dp))
}
Spacer is a component that separates layout elements, taking size modifiers as a parameter.
We are missing weather description so it will be a good idea to add some sun image inside our inner padding.
{ innerPadding ->
Box(
modifier = Modifier.fillMaxSize()
) {
Image(
painterResource(id = R.drawable.sun),
contentDescription = null,
modifier = Modifier
.padding(70.dp)
.align(Alignment.TopEnd)
)
}
}
Box used as a container to fill our whole screen. After we put image inside the box we are able to use align modifier.
Now let’s run the app again.
It looks better but the collapsed Bottom Sheet’s height is not what we are looking for. How should we fix this?
Step 5: Override BottomSheetScaffold defaults
Parameter responsible for the height of the bottom sheet when it is collapsed is called sheetPeekHeight. We can set our desirable height in dp to leave only current temperature and the city visible.
Let’s also remove the default sheet elevation to achieve the transparent look. Add these parameters right after the scaffoldState.
scaffoldState = scaffoldState,
sheetPeekHeight = 168.dp,
sheetElevation = 0.dp
And it’s done!
Be sure to follow me on Twitter for updates and do not hesitate to reach out if you have any questions.