The Fundamentals of Jetpack Compose
Ready to be launched into Android’s toolkit for native UI? Strap in, we’ve got it covered in our blog.
Published on December 16, 2021
by
Filed under development
Just recently, I joined Ars Futura’s Android team. On one of my first days at work, I mentioned that Jetpack Compose is now included in the new Android Studio and that we should check it out. My initial thought was that Compose is probably going to bloom very soon since it is finally coming out with a stable version. And it seems that I was right all along.
Our approach to Jetpack Compose started with an internal workshop for the Android team. It was a 3-hour presentation with hands-on examples of UI implementation and navigation with Jetpack Compose. The team received it really well and wanted to progress in that direction over time.
A bit after that, we got an amazing opportunity to go to droidcon Berlin where a lot of the talks were about Jetpack Compose and transitioning to that type of UI construction in Android development. But, a very important statement that everybody was emphasizing is that this transition must be done gradually and slowly on production applications. Fortunately enough, there are many ways of approaching the migration of UI from XML to Jetpack Compose.
The goal of this blog post is to share the basics of the knowledge I collected about Jetpack Compose in these circumstances. I encourage you to pursue this post even if you're a beginner Android developer. It will surely be beneficial for your Android skills.
What separates Jetpack Compose from the usual type of UI development is the fact that it is written in Kotlin, a programming language used to achieve business logic while developing Android apps.
Applications can now be pure Kotlin without learning any additional languages and concepts, with the exception of thinking a bit differently to create UIs in Compose.
Designing a UI in Compose and passing data to it is very simple. We mostly use @Composable functions which we call components. We pass the data through parameters of that function so components can react adequately to the passed data or states. Composable functions replace the View elements that are usually designed and connected to the data with Kotlin code later. In Compose we can pass that data directly to the view!
If the components are called in the setContent function, we will get a bunch of items positioned one on top of another. That’s where the attributes of Compose elements and Modifier come into play.
Also, if we want to see changes in our layout, all of the code for the preview must be compiled and annotated with @Preview. This is the only thing that makes it “worse” than a View type of creating layouts – XML changes are immediately available to preview in the Android Studio.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
Greeting("Android)
}
}
}
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
Greeting("Android")
}
The most basic Compose elements are Rows and Columns which serve as a LinearLayout type of View in Jetpack Compose. Each of those elements has attributes for basic positioning (verticalAlignment & horizontalArrangement for Rows and reverse for the Columns).
Inside Rows and Columns, we put our Composable functions which we can manipulate.
@Composable
fun Greeting(name: String) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(8.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Text(text = "Hello $name!")
Text(text = "How are you $name?")
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Text(text = "Hello $name!")
Text(text = "How are you $name?")
}
}
}
Each Compose element has a modifier attribute where we assign a Modifier builder to its value so we can manipulate the size, position, padding, click listeners, etc. Also, we can pass it to functions and dynamically edit the properties of an element.
The most used attributes of the Modifier are .fillMaxSize(), .fillMaxWidth() & .fillMaxHeight() which all have 1.0f as a default value. That value indicates that a 100% of our screen’s size / width / height will be filled with that certain element. But, if we assign a different value which is less than 1.0f, it will take up that amount of space on the screen. For example, 0.3f would be 30% of the screen’s size / width / height.
We can make elements clickable by simply adding a .clickable {} lambda block inside a Modifier.
Also, while entering parameters in dp or sp, we must use extension functions that represent the adequate measurement unit.
A very important attribute of a Modifier is that everything we put into its builder will be applied sequentially. Let's look at the code snippet provided below.
@Composable
fun ThirdLesson() {
Column(modifier = Modifier
.background(Color.Green)
.fillMaxHeight(0.5f)
.fillMaxWidth()
.border(5.dp, Color.Magenta)
.padding(5.dp)
.border(5.dp, Color.Blue)
.padding(5.dp)
.border(10.dp, Color.Red)
.padding(10.dp)
) {
Text(text = stringResource(com.example.composeLists.R.string.john))
Text(text = stringResource(com.example.composeLists.R.string.smith),
Modifier.clickable {
})
}
}
Based on the code snippet above, the preview will look like this:
Speaking of states, State is one of the most important things in Jetpack Compose.
State, as can be assumed from the name, is the current state of a UI component or one of its attributes. Each time the state changes, the changed element or simply the attribute recomposes! This is what separates Jetpack Compose and XML by a galaxy. XML used to refresh the whole view only to show that one little text change on the screen.
A very important attribute of state is that we always have to assign it to the variable with a remember lambda function. That is because that value would restart to its initial value with every recomposition and that’s not the behavior we want. We want our states to be remembered even after changing and recomposition.
@Composable
fun ColorBox(
modifier: Modifier = Modifier,
updateColor: (Color) -> Unit
) {
Box(modifier = modifier
.background(Color.red)
.clickable {
updateColor(
Color(
Random.nextFloat(),
Random.nextFloat(),
Random.nextFloat(),
1f
)
)
}
)
}
Ahhhh, the lists!
One of the best things about Jetpack Compose are the lists. They replace one of the most used XML elements in Android development, the RecyclerView.
You can forget about writing adapters, view holders, and all the “beautiful” things that XMLs RecyclerView brought with it. With LazyColumn (or LazyRow for the horizontally oriented) and items(Indexed) function, we can make a “RecyclerView” in no time.
Also, we don’t need to do indexing manually, the itemsIndexed function will do that for us. The items and itemsIndexed functions require only the amount of items or list of items that we want to display in a list. That makes it super simple to use and modify.
In the example below we create a scrollable list containing 2 types of elements and we’re producing 5004 of them which is incredible.
@Composable
fun EightLesson() {
LazyColumn() {
itemsIndexed(
listOf("This", "is", "Jetpack", "Compose")
) { index, string ->
Text(
text = string + index,
fontSize = 24.sp,
fontWeight = FontWeight.bold,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 24.dp)
)
}
items(5000) {
Text(
text = "Item $it",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 24.dp)
)
}
}
}
Scaffold is a Compose element that allows us to use Material design elements. It is required for Snackbars and similar elements that we want to display or customize inside its scope.
Scaffold requires scaffoldState for initialization. ScaffoldState is assigned to a variable with rememberScaffoldState().
Displaying a snackbar in Compose requires a coroutine scope which is usually declared with rememberCoroutineScope() but in this case, we want to use LaunchedEffect() with a key inside it. LaunchedEffect() reacts to each recomposition and key change. All of the conditions must be valid before showing the snackbar. If the state of conditions changes at any moment, our snackbar won’t be visible anymore.
@Composable
fun TenthLesson() {
val scaffoldState = rememberScaffoldState()
Scaffold(scaffoldState = scaffoldState) {
var counter = remember {
mutableStateOf(0)
}
if (counter.value % 5 == 0 && counter.value > 0) {
LaunchedEffect(key1 = scaffoldState.snackbarHostState) {
scaffoldState.snackbarHostState.showSnackbar("Hello")
}
}
Button(onClick = {
counter.value += 1
}) {
Text(text = "Click me: ${counter.value}")
}
}
}
This article is focused on the main features and the implementation of Jetpack Compose in new and upcoming projects. It can serve as a starting point for learning Compose if you want to improve your Android knowledge and go above and beyond with new technologies for implementing a UI layer in Android applications.
Compose seems so flexible, intuitive, written in a language familiar to Android developers, and most importantly, it is very easy to understand!
Also, this technology has been released recently so there was no time to migrate any of the current projects to Compose just yet but we are looking forward to it!
My Jetpack Compose journey was supported by droidcon Berlin 2021 and further materials:
I must note that I have had amazing opportunities since coming to Ars Futura. The company didn’t put a huge amount of pressure on me since I am a new developer. Instead, my coworkers and CEOs embraced my will to learn new technologies and present them to the rest of my team. Also, I’ve learned much from visiting droidcon Berlin which is one of the best and biggest Android conferences in the whole world.
I want to thank everyone involved in this blog post, the Ars Futura Android team for reviewing and Lea Metličić for formatting.
I am enormously grateful for everything that is going my way.
Join our newsletter
Like what you see? Why not put a ring on it. Or at least your name and e-mail.
Have a project on the horizon?