Mastodon

Catching Up on CatchUp: 2023

CatchUp turned 7 recently. This is a little overview of where it's been and where it's going.

CatchUp turned 7 recently. I started the project in the spring of 2016 and open-sourced it a little over a year later. Its development has ebbed and flowed over the years, but it still serves its primary purpose well: a playground.

In its lifetime, I've used it to learn or tinker with different tools, architectures, languages, and more. In this post, I want to give a little update on what I've been doing with it recently, and ideas of where I want to go with it.

State of the Project

CatchUp in 2023 is a Kotlin Android app written fully in Circuit and Compose. It uses a number of tools and libraries under the hood including Coroutines, Datastore, Material3 (M3), Apollo, EitherNet/Retrofit/OkHttp, SqlDelight, Dagger+Anvil, and AndroidX Paging.

Some of these have been in CatchUp for years, but many are new! I've been working on rewriting most of CatchUp over the past six months or so to modernize it.

Code Diffs

After this rewrite, CatchUp's codebase saw a 20% reduction in Kotlin LoC and a 50% reduction in XML 😮.

Before

--------------------------------------------------------------
Language    files          blank        comment           code
--------------------------------------------------------------
Kotlin        267           2338           5540          16647
XML           129            503           1892           3714
Java            3            122            167            831

After

--------------------------------------------------------------
Language    files          blank        comment           code
--------------------------------------------------------------
Kotlin        231           1654           3683          14318
XML            80            215           1156           1623
Java            1             31             26            252

Circuit

Circuit is a library I co-authored working at Slack and maintain. It's a multiplatform, compose-based architecture for Kotlin applications. These days at Slack I work on developer experience, so I don't get as much internal exposure to developing with Circuit as I'd like. CatchUp gives me a good outlet for that, and rewriting CatchUp's architecture from standard Android activities/fragments was a good exercise in both proving out its production-readiness as well as finding areas we could improve.

CatchUp is now single-activity, fragment-less, and MainActivity only has around 40 meaningful lines of code before jumping off into Circuit for the rest of the app.

Some of my favorite parts of this have been how much simpler every screen of CatchUp is now, with clear separations of concerns and easy extension. Writing new screens is dead-simple to both write and wire up. Writing presentation logic in Compose is :chefs-kiss:. The biggest win with going all in on Compose though is the ability to take (edit: mostly*) full control of config changes, allowing CatchUp to actually avoid activity recreation entirely and just reacting to live Configuration sources instead. This is a massive win for productivity, as it means I don't have to spend any time thinking about how to handle rotation.

One big thing Circuit still needs is improved animation APIs, which we purposefully punted to wait for LookaheadLayout to come along more.

Compose

CatchUp's UI was mostly standard Android views/xml, with a tiny bit of Compose that I'd previously dabbled with in service ordering left over from a couple years ago. Rewriting all of this UI in Compose was daunting at first, often frustrating during, and ultimately incredibly rewarding after. Compose UI is a substantial QoL improvement over the olden days of Android UI work.

Compose also makes supporting different screen sizes pretty simple, and some of the new APIs Google's been writing for tablets and foldables are great. CatchUp has extremely basic support for them, but only really as far as trying to better fit content on wide screens for now.

I also implemented support for M3's dynamic theme support more or less seamlessly.

That's not to say it's perfect though. There are some things about Compose UI that really bug me after this experience.

I'm optimistic that some of the above can be improved with time, and definitely don't consider them to be blockers of any sort.

SqlDelight

I migrated from ROOM to SqlDelight for a couple of reasons.

  1. Multiplatform support (more on this later)
  2. I like SqlDelight's design better

SqlDelight is wonderful. I wish it supported some of ROOM's features more easily (namely @Embedded and easier reading of bundled dbs), and I find its migration/versioning handling confusing at times, but overall I vastly prefer it. ROOM works just fine, and worked just fine for CatchUp for years.

Coroutines

This will be my spiciest take: I struggled a lot with coroutines adoption. I would consider myself fairly good at reactive frameworks and at least not terrible at concurrency, and even then it was roughy at times. Debugging them felt significantly harder than RxJava, and I often found myself feeling like it was just too fucking magic. Incredibly powerful, yes. But at times to the point of being opaque. I prefer them to RxJava (what CatchUp used before) now I think, but mostly because I like structured concurrency a lot. As the author of AutoDispose, I wish it didn't need to exist and structured concurrency is the right solution to this problem space.

RxJava works really well. I actually left much of it in place initially while moving the major API surfaces to coroutines and just interop-ing as needed. The interop APIs are very good.

Build Tools

CatchUp now sits on top of SGP, which is the open source Gradle plugin I wrote and maintain for Slack's internal android repo. Aside from being partial to it and the features it offers, it's been super helpful in allowing us to test out or repro things in other projects than the main android repo first.

Anvil is something we adopted heavily in Slack a couple years ago and it's awesome, no notes. Here's a blog I wrote for Slack about it.

One thing I built in SGP is a tool called Dependency Rake, which sits on top of Tony Robalik's excellent Dependency Analysis Gradle Plugin (DAGP) plugin. It post-processes the computed advice and then applies it to the project's build file. I've gotten this working more or less perfectly in CatchUp, so its build files are nice and exact.

CatchUp chases the latest Kotlin versions pretty aggressively (I'm a member of Kotlin's EAP-champions program and use it as one of my test repos). It's at the point now where it almost compiles successfully with the upcoming K2 compiler, pending (at the time of writing) a new Dagger release.

Misc

Datastore is fine as a replacement for preferences. I like that it's now multiplatform too. The API is a bit awkward and tedious at times like old preferences were, but I think that's more a nature of preferences at this point. The coroutines-first API and semantics is also a nice QoL improvement for reactive prefs.

AndroidX Paging is also fine, it solves a problem we all have to write customs solutions for and solves most of them well enough. There are still oddities in CatchUp where it either sticks with stale data, loads mid-page, or shows two loading indicators. I suspect I'm the bug for those, rather than the library.

Some services CatchUp loads have changed

A simple text screen that summarizes an article about whether buses should be free.
(Spoiler: they should be free)

Finally, I rewrote the image viewer using Telephoto. It's a great.

Future

I have some general areas I want to tinker with more in CatchUp.


*It's been pointed out that you can't escape config changes if your wallpaper changes. Good news is I'm CatchUp's only user and don't particularly care about that.