So I’ve just been to Droidcon 2019 and it was full of some amazing talks. Among them was the Jetpack Compose talk by Łukasz Wasylkowski and well… it brought a few mixed thoughts really.
Let’s start with the what. Jetpack Compose is a new UI Framework built by Google for Android that completely takes over the XML layout way of building views which we’ve been doing since the birth of Android.
Views would be built through compose functions which are similar to Kotlin’s Coroutine suspend functions in the way that you start by invoking one of them to start a whole chain of compose function which all work to compose your view hierarchy.
@Composable fun App() { val image = +imageResource(R.drawable.panda) MaterialTheme { Column( crossAxisSize = LayoutSize.Expand, modifier = Spacing(16.dp) ) { names.forEach { name -> Hello(name) } HeightSpacer(height = 16.dp) Button(onClick = { foo() }) { Text(text = "Click Me") } HeightSpacer(height = 16.dp) Container(height = 120.dp, width = 120.dp) { DrawImage(image = image) } } } } @Composable fun Hello(name: String) { Text (text = "Hello $name!") }
Why do they want to replace the XML View Maker?
These reasons are taken straight from the Jetpack Compose talk by Łukasz.
- The current toolkit is already 10 years old and it is full of redundant views and properties from its long history
- The current toolkit’s primary sharing function is inheritance. The primary example of this is every view is a Button extends TextView because it shows text, and an ImageButton extends ImageView because it shows an image, but there’s nowhere where the actual button functionality is shared
- State management can sometimes
- Logic, layouts, styles, etc. are all scattered all over the place.
- Views can be hard to test (even with Robolectric and Espresso)
So what do they hope to bring to the table with Jetpack Compose?
- Easy to update without updating the Android system
- Smaller API surface. Less redundant choices for one particular solution. There should still be enough flexibility to achieve different results reliably.
- Clear state ownership and event flow
- Less code for creating whole layouts
- Easily testable as its all Kotlin code
And to top things off, Jetpack Compose is compatible with the current UI toolkit.
Download Android Studio 4.0
As of the time of this writing, Jetpack Compose is only available on the canary build of Android Studio 4.0. Download it here to get the best experience using Jetpack Compose thanks to features such as the live preview.
Configure your Project for Jetpack Compose
Configure your project-level build.gradle file. You need to use these experimental versions of both gradle and the kotlin-gradle plugin.
buildscript { dependencies { classpath "com.android.tools.build:gradle:4.0.0-alpha01" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.60-eap-25" } }
Then add these dependencies to your app-level build.gradle file.
implementation 'androidx.ui:ui-tooling:0.1.0-dev02' implementation 'androidx.ui:ui-layout:0.1.0-dev02' implementation 'androidx.ui:ui-material:0.1.0-dev02'
Now you’re good to go and ready to develop with Jetpack Compose.
How to Build UIs with Jetpack Compose
Jetpack Compose replaces the standard setContentView() method with a new setContent. You’d use this to start a chain of Composable functions which work together to build your UI. This is of course, analogous to the launch keyword for coroutines.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { HelloWorld() } } @Preview @Composable fun HelloWorld() { Text (text = "Hello World!") }
Composable functions are of course annotated with @Compose, and they’re analogous to suspend functions for coroutines.
Live Preview
Also notice the @Preview annotation. This annotation tells Android Studio to build a preview as you might guess that should show up in a window beside your workspace while you have your Activity class open.
The requirements for a Preview to work is that the function it annotates shouldn’t take in any parameters. It doesn’t have to be used in setContent so if all your other Composable functions take in parameters, you can create a function specifically for preview that starts these functions with some hard-coded data.
If it after following these requirements, it still doesn’t show up, try building your project and it should show. At least, that’s how I expected it to work. Though when I followed these simple steps, all I get is this
I even tried reconfiguring (this version of) Android Studio from scratch with all the default settings and the results are the same. This is the Canary version of Android Studio 4.0 so I’d be crazy if I didn’t expect it to fail somewhere.
But enough of that. We still have devices and emulators. Let’s get back to the code.
Chaining Composable Functions, Styles, and Inputs
Just like suspend functions of course, you can chain Composables one after the other.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { App() } } @Composable fun App() { MaterialTheme { HelloWorld() } } @Composable fun HelloWorld() { Text (text = "Hello World!") }
This is essentially the same except now our layout is created with the Material Theme and HelloWorld() is now called from another function App() which we’ll use to compose the majority of our layout.
It should come as a no-brainer that Composable functions can take parameters.
@Composable fun HelloWorld(name: String) { Text (text = "Hello $name!") }
Layouts and Lists
If we add more views to our App() function like this
@Composable fun App() { MaterialTheme { Hello("Eric") Hello("Monty") Hello("Ben") } }
We’re going to end up with a gobbled mess.
So do we have to bring back our old Linear Layouts and Constraint Layouts? Well, not exactly
Column and Row
What if we were to write our layout like this
@Composable fun App() { MaterialTheme { Column { Hello("Eric") Hello("Monty") Hello("Ben") } } }
Suddenly, our result looks so much better
You can also use Row for similar horizontal effect.
Lists
If we had more than 3 names though, it’d get tedious to have to manually call Hello for each one. One advantage of using Compose is that we’re running Kotlin code here, so we can use a standard loop to create a list.
val names = listOf("Eric", "Monty", "Ben") @Composable fun App() { MaterialTheme { Row { names.forEach { name -> Hello(name) } } } }
And just like that, we get our list with no hassle. No need for adapters and all their massive boilerplate code. This is concise Kotlin at its finest.
Attributes and Modifiers
Of course, to get our views the way we want them, we’ll need to modify them with all sorts of attributes from colour to spacing.
@Composable fun App() { MaterialTheme { Column( crossAxisSize = LayoutSize.Expand, modifier = Spacing(16.dp) ) { names.forEach { name -> Hello(name) } } } }
I’ve added two attributes to my column.
- CrossAxisSize – Determines the size of the column on its cross axis (in this case, the horizontal). Expand makes it go all the way, essentially filling up the width of the screen.
- Modifier – An attribute for miscellaneous properties such as spacing.
As you’d guess, there are loads of attributes you can apply which differ from view to view.
Other Jetpack Compose Elements
There are too many individual elements to cover in one tutorial, but I hope I can at least get you through the ones you’ll be using most often.
Image
val image = +imageResource(R.drawable.panda) DrawImage(image = image)
We can get our image from resources, then use DrawImage(). Doing this however will stretch the image to the whole size of the screen, so wrap it in a container to style and size it.
Container(height = 120.dp, width = 120.dp) { DrawImage(image = image) }
I don’t think I need to explain what the height and width attributes do.
Spacers
HeightSpacer(height = 16.dp)
Height and Width Spacers do one simple thing: take up some space. Often, this is a more elegant solution than making use of margins and padding.
Dividers
Divider(color = Color(0x14333333))
1dp-height views have finally been recognized and put in as a true part of the ecosystem as dividers.. although you can still specify a custom height if you wanted to. The point of dividers is they reach out all the way across, unless you use a modifier to specify margins.
Button
Button(onClick = { foo() }) { Text(text = "Click Me") }
We can finally make buttons clickable without having to make our click functions public. Cheers to clean code and less setOnClickListeners!!
A cool trick you can use is you can make use of Android Studio’s auto-complete descriptions to see what attributes an element can take
Opinions. Will I use Jetpack Compose?
I got the chance to have a little chat with Łukasz. From what he’s saying, it looks like they want to push Jetpack Compose as an eventual replacement to the old UI toolkit, just as Kotlin is to Java right now. This will of course, not happen immediately at all.
At first, I wasn’t exactly sold on the idea. Saying goodbye to adapters sounds great, but what if I want to pick out a specific item in a list and perform some operations with it? With Compose, if you want one item in a list to appear differently from the rest, you need to make use of if statements and the like.
Then it dawned on me. Why would I need to grab data from the single item in the adapter? All logic must be performed in the Presenter/ViewModel and with the better State Management that Compose has over the old UI Toolkit (more on State Management another day), we can more easily stay true to solid architecture.
Jetpack Compose is not ready for production use, so I wouldn’t endorse replacing your UI toolkits just yet in your production apps, but I see it being a strong replacement for the UI toolkit moving forward. We’ll have to rely on the collective effort of both the Android Team as well as Android Developers from all over the world to evolve the new toolkit, figure out solutions and fill out the StackOverflow questions for what may be the next standard Android UI Toolkit.
View the Sample Code on Github
If you want to see the code I used in this tutorial, you can view it here on Github. Keep in mind this is super bare-bones at the moment and only contains as much as I’ve shown in this tutorial. I will however expand on this app and add more Compose related solutions along the way.