Tech Talk: "Declarative UI on Android" mit Adrian Tappe von hi.health

Tech Talk: "Declarative UI on Android" mit Adrian Tappe von hi.health

Hello! Today I’m going to talk about declarative UI on Android and when or if we’re going to adopt Jetpack Compose, the new declarative UI toolkit. So, I am an Android developer. And I have to say, in recent years developing for Android has come a long way. It's really amazing and this is for me three factors:

  • Tooling – Android Studio based on IntelliJ IDEA.
  • We have a new programming language – Kotlin – which is basically a Java without baggage, that gave us null-safety and other nice features and one of them is a nice way to handle higher order functions. Which is functions as parameter to other functions.
  • And then Jetpack: Jetpack is a set of libraries an APIs that is very useful and it abstracts over various versions of Android because this always has been a pain factor that OEMs wouldn't update their phones and so you never were able to use the new features – unbundled, this is great.

The one thing it is missing is a declarative UI toolkit – other platforms already had one, so you can understand how amazed I was when Google announced that Jetpack Compose will become stable this year, which is Jetpack, which is based on Kotlin, supported by AndroidStudio tooling.

A quick reminder: imperative programming is using statements to change programs or in this case UI state. Whereas declarative programming is expressing the logic without describing the controll flow.

So a new UI-Toolkit – and I did some brainstorming on what do I want from UI and the UI-Toolkit from the developers-perspective:

  • Code centricity – that’s one thing I want.
  • I don't like contact switches that much.
  • I like tree structures as a data structure.
  • When I have something I want to reuse it.
  • And I want UI to be a function of state – so on state update in my model, the UI should update. And when someone interacts with the UI, then my state should update and this state should be a single source of truth and not multiple states that I need to synchronize.
  • When adapting a new UI-toolkit I want it to be seamless, which means I cannot tell my product manager „I’m off for six weeks to adopt a new tool kit. I’ll be productive then in one and a half month.“ So it should be easy to learn or there should be some ways to still use my old paradigm when I don't know any further.
  • And of course, my existing coach would still work.
  • I don't want any build issues that could become a blocker.
  • And when my UI is on the phone, it should run stable.
  • And of course I want a happy designer, which means the UI toolkit should be powerful enough to cover all cases.

So what is the current state of UI on Android? Well, Android is more than 10 years old, so it has grown over the years and has some baggage. You can see that the view is the most basic widget has more than thirty thousand lines of code. And the second most basic widget, which is TextView has thirteen thousand lines of code. And when something goes wrong you usually look up what's happening in the code and you cannot do that with that many lines of code.

And then when something you think now it becomes interesting. Let's look at a button. There is some interaction there. It has effectively six lines of code and it derives from TextView. So this somehow works and we've learned to use that, but it's time for something new. And also, how do you handle and how do you work with the UI-system? You usually have a layout written in XML that has – once inflated – has a lot of state and then you have your model that also has state, which is a duplication of state for which you need to write a lot of glue code – that synchronizes that. So really, you have XML and this you inflate and then you find the elements by ID, which is not typesafe by the way and modify it with all problems that come from it because on the mobile device, UI might be gone and you still try to access it. And yes, nowadays this can partially be auto generated, but this still is a bit flaky and brings some other problems with it.

So my evaluation in emojis is like this: I gave it four thumbs up for learning and adopting just for the fact that it's the system we use. It's already adopted. We already know how to handle it.

Let's look at Compose with an example, the Hi.Clickable - which looks like this. It's a surface, a frame with a logo, a text and when you click on it, it counts up thumbs up until you have five – then it shows a star emoji.

On the left side you see all code that is needed for that. And it's a bit small if you can see it – I will zoom in later. This is all Kotlin and it looks like scoped function calls and that's what it is – a tree of function calls. So it really is 100% Kotlin as well for layouts, you see row-column-row layout elements as well as for the widgets, so image and text.

It's code centric! Yay, no context switches and as you've seen, this nice scoping, which looks like a domain specific language, is in fact real Kotlin done with two tricks. The one is Kotlin-trick that you pass in – I've already talked about it – a lambda, which is an anonymous function to a higher order function in order to present context. And in Kotlin you can write the last lambda, which is on the lower part – you see this parameter for content – you can write it outside of the parentheses and you avoid the whole closing parentheses hell on nested function calls. And then the other trick is in Compose that a function doesn't return UI. A bit of an abstract concept – it emits UI while being executed, which means if you execute the thumbs up function twice it will emit two thumbs up. So, tree structure – great! And, well – functions, so you really can have your UI as a function of state, it is reactive so when your state updates – here, you see it with a click count – the whole function tree gets re-executed and will emit your new UI for it. If you want – and sometimes you have states that you don't want in your business logic in your model, which is only relevant like scroll state, for example to the UI itself – you can remember it within the function tree.

So state handling – really amazing! And functions are reusable components per se, so this works and if you have some anonymous functions, lambdas that you say „I want to reuse them“ – just use IntelliJ, say extract method, extract function and you have your new component from something you've written – great! Of course re-executing the whole tree wouldn't be very efficient. So as you see this annotation @Composable; there is a Compose compiler-plugin for Kotlin and there is a runtime that uses the fact that if a function has the same input, it will have the same execution flow and output. So it doesn't really need to be re-executed. So if you have a large state, not just one integer as click count, but larger state and only a part updates, most things don't need to be re-executed. And as it’s 100% Kotlin you can use Kotlin control-flow. When – which would be a switch statement in other languages – you can use repeat, for-loops and so on. Here you see those thumbs up being executed two times if the click count is two and it will emit the thumbs up twice. I already talked about IDE support for refactoring and search – this is great. And there are also already a lot of standard components, for example the whole material library, that really serve as examples. So they're not thirty thousand lines of code they're written in Compose themselves. So it's a nice example to look at – it's code, great for a developer.

The last thing that's really needed is interoperability and it exists. So you can have in your XML – if you are in your legacy system – one widget called Compose view where you then can start composing your functions. And if you already have some components in your legacy system that you want to reuse in your new one you can have one compose function that internally can inflate your XML and do those things. This really is great and I really hope to make our designer happy very soon.

This all was just great – great experience when learning it! But there is a catch: it is still in beta and it has a tight coupling of Compose – the Compose compiler, the Kotlin compiler and the studio preview. And this comes from the fact that in order to develop that, all elements had to be developed in parallel at the same time. I hope this coupling will be lifted soon but for our example we had, it did not work Compose beta 6, it didn't work in 7, fixed in 8 very quickly. So this was great. But when testing it in our main app, then our main app didn't compile in one specific version which happened to be the one coupled to the Compose version that I needed for my example. Today I have seen the new versions have come out of both so as of today it works, but this is so kind of build instability yet that I wouldn’t rely on in a productive system. So are we going to adopt Compose? Yes we will, of course! We will or we are already preparing the app when adding new features we have it in mind to later migrate to Compose with the compatibility features and interoperability features we are going to do this little by little, but we’re going to wait until it’s stable which hopefully happens later this year.

If you are that amazed like I am please look at the resources! They are really great talks from google I/O and the documentation is really great so please have a look – thank you!

 

Technologien in diesem Artikel