Sparking Jetpack Compose at Tinder

Tinder
Tinder Tech Blog
Published in
9 min readAug 15, 2022

Author: Tasha Ramesh, Staff Software Engineer

It’s an exciting time to be a mobile engineer. Now that declarative UI has finally made its way into Android with Jetpack Compose, I was fascinated about how this new tech could improve the development of Tinder’s features. Given its promise of simplifying and accelerating development while increasing the fun factor exponentially, we decided to take a stab at introducing it into our ecosystem.

Adding any new tech into the fold of a codebase as large as ours is easier said than done, and Jetpack Compose was no exception. Even though it was mostly just geared towards UI development, we wanted to get a head start with integration before Compose’s stable offering, especially to study its holistic impact on the app’s ecosystem. Here’s a look at how we explored Compose’s early alpha/beta cycles at Tinder and finally integrated its 1.0 release!

Gathering Resources

In Compose’s early alpha stages, I started an internal repository (called tinder-compose-samples, inspired by Google’s android/compose-samples) with some implementations of common Tinder screens. At first, this was just meant to be a small playground for Compose that would hopefully serve as a learning tool for the team when it would later be adopted.

Experimenting with Compose for Tinder screens

For some members of the team, seeing Compose’s simplicity (and a rudimentary implementation of the Tinder card stack) in that repository moved a needle for wanting to work with the new tech. This playground allowed us to tinker around with Compose’s alphas, betas, and rc’s and helped us to get a feel for its integration into the main codebase.

Compose Working Group as of June ‘22

Getting Compose into the codebase required expertise from multiple teams and multiple points of view; this was a perfect candidate for a collaborative, engineering-driven initiative, which is highly valued at Tinder. After many (many) Slack groups, DMs, and one-off conversations, a working group formed out of several teams within the greater Tinder Android team to start tackling the elephant in the room. Declarative UI was coming, and we needed to get with the program (pun intended).

Assembly

Declarative frameworks are quite the paradigm shift, especially for a large-scale production app built mainly using a decade-old imperative UI toolkit (with some Java still sprinkled here and there). Plus, this one comes with a Kotlin compiler plugin which was something we hadn’t encountered before. And so, we arrived at the working group’s first task of analyzing Compose’s impact on the build system and developer experience:

  • Kotlin: Compose (compiler) versions were/are tied to Kotlin version/vice-versa until Kotlin makes compiler plugin APIs stable. Since this was our first time using a library with a Kotlin compiler plugin, it was our first taste of a dependency that would set the terms for Kotlin upgrades moving forward. This did hinder us from depending on some of the Compose alpha releases, so we had to wait until the stars (i.e. Kotlin/Compose versions) aligned — something we are still learning to live with. Thankfully, this compatibility map between Compose Compiler and Kotlin versions now exists (and 1.2.0 finally introduces some independent versioning!).
  • Android Studio: Kotlin language versions, Android Gradle Plugin (AGP) versions, and Android Studio versions all needed to be in sync. For releases and mainstream development, we always depend on stable everything, lest we create an unstable CI environment and appalling IDE experience for the broader Android team. Luckily, at least two of these would already be sync’d: AGP & Android Studio, whose channels release in tandem. While the rest of the team continued on stable bundles, the working group started experimenting with the preview channels.
  • AGP: While we experimented with the non-stability of AGP and Android Studio, we were able to get a preview of how Compose would fit into our ecosystem. However, since release trains always build off stable channels, we had to resort to manually adding the Compose compiler (which the then-alpha version of AGP did automatically). A few experimental changes tested the efficacy of using a stable AGP 4.1.2, and manual inclusion of the Compose compiler. The larger Compose/Android community was also looking towards such solutions, which allowed us to follow the approach outlined here. Once applied, these changes allowed us to:
  • Gauge build time impact
  • Gauge transitive dependencies such as alpha versions of appcompat/androidx libraries, all using the stable AGP
  • AndroidX: Compose libraries live within androidx, and so the addition of Compose dependencies, especially androidx.activity:activity-compose, would pull in other related androidx dependencies, including the main androidx.activity:activity (which, during this exercise, was pulling in an alpha version). This was a little scary for obvious reasons, and it gave us our first glimpse of the new links it would add to the (already gnarly) dependency upgrade chain.

Bottom line, wrapping up these experiments while Compose was in its final beta/rc cycles allowed us to be sure of Compose’s effect on our ecosystem well before stable release so that we could just plug-n-play with 1.0.

It’s Alive!

The possible impact of Compose libraries on Tinder’s stability in the wild was a huge unknown, so a good first-adoption candidate would have to be something that would not lie in the critical user paths and would receive moderate to low traffic. It also needed to be a true sandbox, one where we could take our time using Compose without being mired by other features, deadlines, and such.

The Licenses button in the settings screen

Tinder’s settings screen includes a “Licenses” button that pops up all the open-source libraries with their licenses used in the app. This was quite the legacy screen, and sported an outdated UI of a small dialog with scrollable content. Since it was a completely isolated portion of the app that received very little traffic, it was the perfect opportunity for a Compose rewrite, especially since at the core, the screen was really just a simple list of clickable items.

Around this time, the newest member of the Tindership intern program was onboarded onto this project and the working group (shout out to our intern, Robert Zhao). We all worked together right from Compose basics, using our internal tinder-compose-samples repo as a playground to develop prototypes of the screen. Once we grasped some best practices around Compose and declarative UI in general, we finally incorporated some production-grade Compose into the main Tinder app for the new and improved Licenses screen.

The Compose-ified Licenses screen

Even though some of us had played around with Compose before, it was still incredibly satisfying to transfer ideas so quickly to code, and to see all this live in the Tinder app (check it out for yourself in Settings -> Licenses!)

Tinkering with Tinder Tools

Having the Compose-ified open-source licenses screen out in the wild was a huge win, and yet, only the beginning of the story. The positive, or rather, the lack of negative outcomes of the first-adoption candidate paved the way for us to consider more instances where we could leverage Compose.

Declarative UI can be very alien to Android devs, unless they’ve spent time with things like React, or SwiftUI on iOS. Compose is to the existing Android UI toolkit as Kotiln was to Java, or Coroutines was to Reactive programming — these ideas need to be constantly applied to truly grasp them.

While adopting Coroutines, a small subset of us tried to replace RxJava implementations and incorporate the new APIs in some smaller features and internal tools to increase our proficiency with them. Once we felt comfortable with using Coroutines in our codebase, we were able to formulate best practices and recommended guidelines to allow the rest of the team to use those APIs safely.

This strategy was followed for Compose adoption as well, first migrating a few internal tools over to Compose and adding some new ones in Compose directly before moving on to actual user-facing features. This was, and still is, enabling us to formulate some of our own ideas around best practices, as well as grok first-hand what the greater Android dev community had learned without affecting product cycles.

All (Design) Systems Go

Armed with Compose’s in our ecosystem, we approached a then-nascent design system component library, which was about to start out with its first reusable component. Creating custom components in Android Views comes with its own unique challenges and awkwardness (*cough* inheritance *cough*). Compose clearly came out as the less cumbersome alternative for creating reusable components and for providing a mechanism for developers to easily access design tokens. However, at the time there was barely any Compose usage in the codebase. A choice had to be made: choose Compose for developing all design system components moving forward, or, develop them as Views and cross the Compose interop bridge when we get to it.

Betting on Compose all the way, we opted for the former and greenlit a project to pivot our in-house design system development to be Compose-first. For this, we opted for a hybrid solution of both customizing and wrapping the Material theme with extra tokens, instead of either extremes of discarding Material completely or solely customizing Material itself.

This gave us a safety net to allow for Compose adoption to proceed as reusable components were being iteratively introduced. Thanks to how Compose libraries were built — as well thought out layers — we’d be able to customize Material components for some, but for others we could drop down to working with lower-level foundational components.

Hierarchies of Tinder’s design system components

We knew that widespread adoption of Compose in our codebase was coming at some point, so these decisions would allow us to have a cohesive design system in Compose ready by that time. For any new or existing Android View-based UI in the interim, we decided to offer Android View wrappers (using AbstractComposeView interop) of our Compose components to bridge the gap. Hopefully, this would make it easier for teams to develop, with or without Compose directly, while still adhering to a unified design language.

// TODO

Perhaps one of the challenging (and yet intriguing) aspects of Jetpack Compose — and virtually anything new — is that we are essentially diving into the uncharted. It was, and very much still is, up to us to play around with it to gain fluency. As cliche as it might sound, Compose’s mental model really might ask you to leave old (Android View) habits behind, open your mind, and let in new ways of thinking.

These efforts have been exposing the true learning curve of this type of framework, and its effects on our dependency chain management. We are now at the stage of vetting some of the finer aspects of Compose in our ecosystem, such as performance with Android View interop, lint rules, and detailed dev guidance, before finally opening the doors to more Compose. The journey so far has inspired us to build a library of best practices and learnings to help fellow engineers — within the company and eventually beyond — to develop their ability to leverage Jetpack Compose.

Stay tuned as we open-source learning and development for Jetpack Compose, deep dive into our Compose-first design system, and share wherever our Compose voyages take us next!

Want to work with fellow engineers who aspire to do great work and make an impact within the Android Dev community? We’re hiring!

--

--