A React Dev's Preliminary Thoughts on Jetpack Compose
I recently, unexpectedly, found myself learning the basics of Android development so I could manage a large project. When it took longer than expected to find the right people to do the development, I wound up also applying some of this knowledge by porting over a very large amount of code from Python and TypeScript to a prototype app. The goal was to get a head start on implementing business logic, then it turned into business logic plus communication with hardware so we could verify some external systems, and finally it turned into that + some toy interfaces so we could easily demonstrate to incoming team members how the business logic could be applied to the view.
This was and still often is a painful experience, as working in an unfamiliar language, framework, and platform so often is. Luckily, while the framework (Android) and the platform (also Android) are often very foreign, Kotlin itself has been a dream. In many ways, it feels like the language I’ve always wanted: the friendliness of Ruby with a powerful type system that has Java’s specificity but feels closer to TypeScript in its expressiveness, with the performance of the JVM. Coroutines are amazing. Now that SharedFlow has been released, it also provides concurrency and message passing that’s as easy as Go’s goroutines and channels. Wonderful!
My classes and their tests moved from Python and TypeScript to Kotlin with absolutely no difficulties. There’s nothing like porting over thousands of lines of code and unit tests to make you feel more comfortable with a language. I did my best to modify it to look more like idiomatic Kotlin but I’m looking forward to having someone review it and taking me to school.
But this isn’t about Kotlin, it’s about Jetpack Compose. I first read about it some months ago and noticed then that… wow, it sure sounds like React for Android. Plenty of comments online said the same thing, or said that it was just Flutter for Android, or SwiftUI for Android, but plenty of people said those were just React for X. As a React developer, this made me happy, since I like the idea of working in other languages and platforms. At the time, I didn’t know if or when I’d apply it. And then…
…I wound up here. It became clear that the best way to prove that my business logic really worked, test our new Arduino-based robot control system, and start testing our new mechanical components would be to provide a simple interface that replicated the states of our legacy product. While I felt very comfortable with Kotlin and reasonably comfortable with Android navigation, fragments, and activities, I did not feel at all comfortable with the classic UI framework. It felt very jQuery to me, like going back in time to the bad old days of web development. Jetpack Compose, on the other hand, had just reached alpha (alpha 7 by the time I got to it) and reviews were positive. Since this was just a prototype interface, it seemed like the best option for slapping something together. The person or people who inherit the app could then decide whether to continue betting on Jetpack Compose or fall back to the battle tested, if cumbersome, classic.
I’ve spent a few hours kicking the tires of Jetpack Compose. Since I only get to be a total amateur for a little while, I thought it would be good to jot down some of my preliminary thoughts while they’re still fresh.
Tl;dr: I like it!
To get it right out of the way, I really like Jetpack Compose! I’d happily continue building with it. I’ve seen comments on Android forums from experienced developers who say that it is the future of Android development and I hope that is true. It feels just like React in so many ways. Almost all of my experience transferred immediately: useState
becomes mutableState
, props are still props, it has its own version of useEffect
. The way you organize components (composables) is the same, so you can think about the view and its state almost exactly the same way you would think about it in React! This is awesome.
I was able to bang out my first prototype interface, a crude recreation of one of our most complex, dynamic interfaces, in a couple of hours. Today, I very quickly prototyped another interface – a simple control panel that spits out some dynamic values coming from sensors and provides controls to load additional information or adjust the rate of the streaming data – in about 45 minutes. All of the methods that interact with the business logic plug right in. I’m not even using their effects yet, just passing props down, and I feel productive.
Some stray thoughts on the positives:
-
The fact that it’s Kotlin all the way down is extremely refreshing. Working in React forces one to context switch constantly: first it’s TypeScript, then it’s TSX, then it’s CSS, oh well here it’s a Styled Component so it’s TS-in-CSS. Being able to just live and breath one language is wonderful. I’ve read about plenty of other languages or frameworks that allow this – Elm comes to mind – and my thought had always been “But I’m good at all these things, I’m productive, it’s what everyone does. Why change it?” Avoiding context switching is real!
-
I think I like the fact that the builtin layout composables are so rigidly defined. That is to say, I appreciate that instead of a
div
withdisplay: flex;
andflex-direction: column;
it’s justColumn
. It’s almost like this is what happens when you design a layout system in 2020 instead of gradually bolting on behavior over many decades… -
I haven’t used it yet but the fact that it has an official Animation library is exciting. This tutorial paints a great picture.
-
I really really really like being able to create
MutableState
inside of aViewModel
. Hell, I really likeViewModel
in general. One of my biggest gripes about React right now is the way it can hide complexity in hooks, letting some component deep down in the depths of a view subscribe to some external event and hook into all kinds of crazy side-effects. Hooks also force you to think exclusively in terms of side-effects, like you’re building a huge Rube Goldberg machine. By comparison,MutableState
in aViewModel
feels like the best of worlds. Data flow is still unidirectional – I’m still feeding props down through composables – but the option of having some of this logic originate in an isolated world dedicated to business logic makes things a lot more straightforward. -
It’s nice that the body of a composable is just Kotlin. Not some Kotlin-markup hybrid where you can use expressions but not statements ala TSX/JSX – full-on Kotlin. Even though I don’t feel limited by TSX these days, I think this will cut down the learning curve for new devs significantly compared to what one finds with React. Being able to use a
when
expression or assign variables right in the body of the code is freeing. It’s also a double-edged sword that I’ll comment on later.
It’s not all roses, though. Some of my immediate concerns:
-
The in-IDE previews are slooooow. I find that I don’t preview things as much as I’d like to because every save causes a rebuild that takes forever. I’m sure this must be part of its alpha-ness, but it really stands out in contrast with how well the rest of it seems to work.
-
As much as I appreciate the explicit, rigidly defined composables (
Column
,Row
, etc,…) and their unique styling parameters and modifiers, I worry that it’s going to take forever to really get fluent in making things look good. Say what you will about CSS but at least you can be reasonably sure that most of the properties you know for one element will work on another. Maybe that’s too generous, but at least you’ll know where to start. This isn’t as true with Compose. While I picked up positioning of composables and their contents pretty quickly, making things look the way they do in the wireframes is still a challenge because I’m constantly digging into them to see what they can do. -
The aforementioned issue wouldn’t be as much of a problem if not for the lack of best practices and examples out there. Almost worse, a lot of what you can find right now refers to earlier versions and removed APIs. Luckily, I’m not trying to build something for production and the rest of it is familiar enough that I’m confident I can invest some time in just about every issue and still be ahead of schedule. But it’s worth considering if you’re not confident with the mental model required to build this kind of app.
-
It’s going to need to improve its application state management story. While Context works fine for most of us these days and Compose has the corresponding Ambient, there are still cases where the web needs Redux and it seems likely that the same will be true for Android. There is a ReduxKotlin project in the works, so maybe it will take off. Or maybe there’s a better option, something more appropriate for this ecosystem.
-
It’s also going to need a better solution for forms. My kingdom for React Hook Form for Jetpack Compose!
A last one that I want to expand upon beyond a quick bullet point:
I worry that the ability to mix composable function calls and Kotlin code could lead to readability and predictability challanges.
The TSX syntax places limitations on what I can do once I start describing the view. Flow control is limited, side-effects are verboten. It’s predictable, it’s clear. It can be noisey if someone abuses ternaries or boolean statements to provide flow control, but we can train ourselves to expect it.
Jetpack Compose enforces no such order. I can do anything, anywhere, at any time. This can be nice if used sparingly but it doesn’t take much to imagine it going off the rails. Picture a wall of view code where, for some reason, a variable is reassigned 200 lines deep. Suddenly, the predictable view is a little less predictable. Refactoring is just a little trickier. Composables become just a bit more complex and harder to predict. It will be important for the community to establish best practices around this!
So that’s where I stand right now. It’ll be interesting to review this in a few months and see how much of this was accurate. In the meantime, I am optimistic.