Dive in Jetpack Compose

Original author: Thijs Suijten
  • Transfer
Hello. Before leaving for the weekend, we hasten to share with you another translation prepared especially for students of the Android Developer. Advanced Course .



Trying a new UI framework for Android applications


Over the past few years, participating in many mobile projects, I had to use various technologies, such as Android, ReactNative and Flutter. Switching from ReactNative back to classic Android made me mixed feelings. The return to Kotlin went well, but I really missed the React UI framework. The small reusable components that make up the user interface are great and provide more flexibility and speed of development.

Back in classic Android, I needed to worry about keeping the View hierarchy as uniform as possible. Because of this, it is difficult to truly devote yourself to the component approach. This makes copy-paste more attractive, which leads to more complex and less supported code. Ultimately, we keep ourselves from experimenting with a user interface that could improve UX.


Android reveals Jetpack Compose. Illustration: Emanuel Bagilla

Jetpack Compose to the rescue


Therefore, after watching What's new in Android from the Google I / O 2019 conference, I immediately began to deal with Compose and tried to learn more about it. Compose is a reactive user interface toolkit fully developed by Kotlin. Compose looks very similar to existing user interface frameworks such as React, Litho, or Flutter.

The current structure of the Android UI framework has existed since 2008, and over time has become more complex, it is quite difficult to maintain. Jetpack Compose aims to start from the beginning, taking into account the philosophy of modern components. The framework is written with the following main goals in mind:

  • Inconsistency with platform releases: This allows you to quickly fix bugs, since Compose is not dependent on new Android releases.
  • Smaller technology stack: The framework does not force you to use View or Fragment when creating a user interface. All elements are components and can be freely composed together.
  • Transparent state management and event handling: One of the most important and complex things that you need to solve in large applications is the processing of data flow and state in your user interface. Compose clarifies who is responsible for the state and how events should be handled, similar to how React handles it.
  • Writing less code: Writing a user interface in Android usually requires a lot of code, especially when creating more complex layouts, for example, using RecyclerView. Compose aims to greatly simplify the way to create a user interface.

This makes it easier to create isolated and reusable components, making it easier to create a new screen with existing elements. Helping you, as a developer, focus on creating a convenient user interface, instead of trying to control the View hierarchy and tame View and Fragment.

A simple application with Compose: Hello World


Let's take a look at the code for a simple Hello World application with Jetpack Compose.

class ComposeActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent { CraneWrapper { MyApp() } }
    }
    @Composable
    fun MyApp() {
        MaterialTheme {
            Text(text = "Hello world!", style = +themeTextStyle { h3 })
        }
    }
}

In the method, onCreatewe set the contents of our application by calling setContent. This is a method that initializes a composite widget tree and wraps it in FrameLayout.

To make it work, we need to wrap our application in CraneWrapperand MaterialTheme. CraneWrapperresponsible for setting up providers for Context, FocusManagerand TextInputService. MaterialThemerequired to provide the colors, styles, and fonts of your widgets. With this in mind, we can add a component Textthat will display our text on the screen in a certain style.

State introduction


Managing data flow and states can be a daunting task. To illustrate how easy it is with Compose, let's create a simple counter application.
To work with states, Jetpack Compose uses the ideas of other modern UI frameworks, such as Flutter and React. There is a unidirectional and reactive data stream that causes your widget to update or “rebuild”.

@Composable
fun MyApp() {
    MaterialTheme { Counter() }
}
@Composable
fun Counter() {
    val amount = +state { 0 }
    Column {
        Text(text = "Counter demo")
        Button(text = "Add", onClick = { amount.value++ })
        Button(text = "Subtract", onClick = { amount.value-- })
        Text(text = "Clicks: ${amount.value}")
    }
}


In the example above, we add the “Add” and “Subtract” buttons along with a label that displays the current number of clicks. As you can see in the example below, updating the “amount” state, widgets are intelligently re-arranged when the state changes.


Starting the demo application The

state is amountinitialized with +state { 0 }. Trying to figure out what kind of witchcraft is, I crawled into the source code. This is my opinion, although I'm still not sure that I fully understand everything.

state {...}creates . A class is a fuzzy class that contains a block of executable code that is executed positionally in the context of a composition. The class contains one value with the type , essentially making this value observable. The + operator is a temporary operator that allowsEffect<State<T<code>>EffectStateModelStatefrom Effect.

Custom state models


Instead of using a +state {}single value to create the model, we can also create a custom model using the @Model annotation. We can improve our counter application by dividing it into smaller widgets and passing the model to other widgets that are updated and display the state of this model.

@Model
class CounterModel {
    var counter: Int = 0
    var header = "Counter demo"
    fun add() { counter++ }
    fun subtract() { counter-- }
}


Using annotation @Model, the Compose Compiler plugin makes all the variables in your model observable so that they can be used to re-arrange widgets. Let's update our widget to use CounterModel:

@Composable
fun Counter(counterModel: CounterModel) {
    Column {
        CounterHeader(counterModel)
        AddSubtractButtons(counterModel)
        CounterLabel(counterModel)
    }
}
@Composable
fun CounterHeader(counterModel: CounterModel) {
    Text(text = counterModel.header)
}
@Composable
fun AddSubtractButtons(counterModel: CounterModel) {
    Button(
        text = "Add",
        onClick = { counterModel.add() })
    Button(
        text = "Subtract",
        onClick = { counterModel.subtract() })
}
@Composable
fun CounterLabel(counterModel: CounterModel) {
    Text(text = "Clicks: ${counterModel.counter}")
}


The only widget that the spinner application consists of is now split into several smaller composable widgets. CounterModelpassed to various widgets, either to display the state of the model, or to change the state of the model using the functions add()or subtract().

No more view


It is important to understand that Jetpack Compose widgets do not use view or fragment under the hood, these are just the functions that draw on the canvas. The Compose Compiler plugin processes all functions with annotation @Composableand automatically updates the UI hierarchy.

For example, a widget Dividerconsists of a widget Paddingthat contains a widget DrawFillRect. Looking at the source code DrawFillRect, it becomes clear that he draws lines directly on the canvas. All other widgets are implemented in the same way.

@Composable
private fun DrawFillRect(brush: Brush) {
    Draw { canvas, parentSize ->
        val paint = Paint()
        brush.applyBrush(paint)
        canvas.drawRect(parentSize.toRect(), paint)
    }
}


The source code of DrawFillRect, which is used inside the Divider widget.
If we look at the Layout Inspector by launching one of the sample applications from Google, we will clearly see that when running the Android application with Compose there are no Viewor ViewGroups. We see the FrameLayoutcontaining CraneWrapperthat we created in the code, from there the Compose UI hierarchy is displayed on the screen.


Layout Inspector inspects Jetpack Compose.

Lack of views also means that Jetpack Compose cannot use currently available views, such asandroid.widget.Button, and should create all widgets from scratch. If you look, for example, at Flutter, which uses the same approach, you can see that this is hard work. This is one of the reasons why Jetpack Compose will need time before it is ready for use in production.

All elements are widgets.


Just like Flutter, in Compose all the elements are widgets. More complex widgets were broken down into elementary widgets with clear responsibilities. Therefore, even padding, spacers and so on are widgets. For example, if you want to add indentation around a button, simply wrap it in the padding widget:

Padding(padding = 16.dp) {
    Button(text = "Say hello", onClick = { ... })
}


Connecting code to the user interface


Combining Kotlin code with UI widgets is very easy. For example, if you want to show a user interface that repeats or depends on some conditions. So, you can easily display a list of names, as shown below.

Column {
    listOf("John", "Julia", "Alice", "Mark").forEach {
        Text(text = it)
    }
}


This is a really powerful feature, but you must be careful not to program too much logic at the user interface level.

Compatible with your Android apps


Compose is designed so that you can add it to an existing application and gradually transfer some parts of your UI to a new framework. The above examples add the Jetpack Compose UI to one activity. You can also embed Compose widgets in an existing XML layout using annotations GenerateView:

@Composable
@GenerateView
fun Greeting(name: String) { /* … */ }
// В каком-то существующем layout.xml

Conclusion


I am delighted with Compose, because it reduces the growing suffering that I experience when developing for Android. It helps to be more flexible, focus on creating a convenient user interface, and clear responsibility also helps to avoid mistakes.

Compose has a long way to go, in my opinion, it can be used in production no sooner than in a year or two. However, I think this is a good time to take a look at the Jetpack Compose. The creators are actively looking for feedback, at this stage you can still make changes. All reviews will help improve this new framework.

Read my Try Jetpack Compose today article to learn how to hook up Compose pre-alpha. Also, I think you will be very interested to seevideo on declarative interface templates with Google I / O.
I look forward to when I can use Compose in real Android applications!

That's all. We look forward to your comments and have a great weekend!

Also popular now: