19 November 2024
A while back, I hit a bug in the Compose Foundation library causing a layout issue. The usual flicker-on-recomposition kind of thing that makes you question reality for an hour before you find the open issue on the Android issue tracker. The fix was already merged â I could see the commit â but it had only shipped in foundation:1.9.0-beta02. The latest stable was 1.8.0. And the next stable release? Months away.
I remember hesitating. We had a blanket rule on the team: stable versions only. No alphas, no betas, no exceptions. It felt responsible. But sitting there with a known bug and a known fix that I couldnât use because of an arbitrary version suffix, I started wondering whether our âstable onlyâ policy was actually protecting us or just making us wait longer for the same code.
Thatâs when I read Jake Whartonâs post on using AndroidX betas, and it reframed how I think about AndroidX versioning entirely. The short version: AndroidX beta01 is not what you think it is. Itâs not an unstable prerelease. Itâs the API-stable release, and itâs been running in Googleâs own apps for weeks before you ever see it.
Most libraries follow standard semver â 1.2.0 is stable, 1.2.1 is a patch, 1.3.0 adds APIs. AndroidX does something fundamentally different, and understanding each stage matters.
Alpha is active development. APIs can be added, renamed, or removed entirely between alpha releases â zero compatibility guarantees. alpha01 to alpha05 might look completely different. Youâre explicitly signing up for churn. The one guarantee is that the library compiles and passes Googleâs internal test suites, which is more than most open-source alphas offer.
Beta is the API freeze. At beta01, the API surface is locked. Google has automated tooling â metalava and the AndroidX API tracking infrastructure â that enforces this at the source level. Any commit that changes a public API after the beta cut gets rejected by the build system. Subsequent betas (beta02, beta03) are purely bug fix releases. They can fix behavior and patch edge cases, but method signatures, class names, and the public surface cannot change.
RC (Release Candidate) adds one promise on top: the team believes no more bugs need fixing. rc01 is typically identical to the last beta. It exists as a âhold and observeâ period â if no critical issues surface, that exact bytecode becomes stable. In practice, the stable .aar is often byte-for-byte identical to the RC.
Stable is the final stamp. But hereâs the thing â itâs the oldest code in the pipeline. By the time 1.2.0 is stamped stable, 1.3.0-alpha01 has been in development for weeks. This is documented in Googleâs own AndroidX versioning guidelines. The naming creates a psychological barrier that makes teams treat beta01 as experimental when itâs actually the production-ready API freeze.
If youâre worried about AndroidX betas being undertested, consider this: all of Googleâs first-party apps â Gmail, Maps, YouTube, Google Pay â ship against the code in AndroidX HEAD. Not against stable releases, not even against betas. They build and deploy with alpha versions and individual commits. By the time a library reaches beta01, it has been running in production across Googleâs entire app portfolio for weeks.
AndroidX is to Googleâs apps what your internal util- and core- modules are to your app module â shared code that lives in their monorepo. The idea that beta01 is risky when Google has been shipping that code in apps used by billions doesnât hold up.
Not all betas are equal, and blindly upgrading everything is not what Iâm advocating. Hereâs how I evaluate whether a specific beta is worth adopting.
Check the release notes and issue tracker. Every AndroidX release has a changelog on developer.android.com/jetpack/androidx/versions. If the beta lists âBug fixesâ with specific issues resolved, thatâs low risk. I always read the full commit list for Compose betas â the delta between beta01 and beta02 is usually 5-15 commits, and scanning them takes two minutes. Then search issuetracker.google.com for the library â if there are open P0/P1 bugs filed against the beta, thatâs a red flag. A beta with zero critical issues after two weeks in the wild is safer than most third-party librariesâ stable releases.
Look at the API surface diff. The AndroidX team publishes API diffs between releases. If youâre on 1.8.0 stable and considering 1.9.0-beta02, check what APIs were added. If none of your code touches the new APIs, the beta is effectively a bug-fix-only upgrade for you â binary compatibility is maintained, so your compiled code against 1.8.0 works against 1.9.0-beta02 without recompilation.
Consider the libraryâs maturity. A beta01 for activity:1.10.0 is very different from a beta01 for a brand-new library in its 1.0.0 cycle. Mature libraries like core, activity, and lifecycle have small deltas between versions â their betas are almost boring. Newer libraries carry more risk because the surface area of change is larger.
Let me give you some concrete examples of why this matters. Jake Wharton shared these from Cash Appâs experience, and Iâve hit similar situations on my own projects.
Compose UI recomposition bugs. Various issues in Compose runtime, foundation, and UI around retained state, layout behavior, and recomposition edge cases. These get fixed in betas, but if youâre waiting for stable, youâre waiting months with a known broken behavior. Your options: live with the bug, implement a fragile workaround, or bump to the beta where itâs already fixed.
The ScatterMap bug in collection. The collection libraryâs ScatterMap had a bug that caused deleted values to be returned during insertion. Thatâs a data corruption bug, not a cosmetic issue. It was fixed in a beta. If you were on stable only, you had a silently broken map implementation.
Graphics shape library breaking bottom sheets. A bug in stable graphics-shape was breaking rounded corners on bottom sheet decorations. The fix shipped in the next versionâs beta, but the stable release was months away.
Navigation Compose deep link handling. navigation-compose had edge cases around deep link argument parsing that silently dropped query parameters. The fix landed in a beta, but the next stable was two release cycles out. Teams on stable had to write manual URI parsing workarounds more fragile than the beta fix itself.
In all these cases, the pattern is the same: a real bug exists in stable, the fix is available in beta, and waiting for stable means living with the bug. The irony is that the âsafeâ choice leaves you running broken code longer.
Hereâs the tradeoff that AndroidXâs versioning creates. By stretching the prerelease period, Google pushes the bug-fix window further out. If youâre on Compose UI 1.8 stable and find a bug, you canât get the fix in a 1.8.1 patch â AndroidX doesnât typically do point releases on older stable versions. Your only path is 1.9 stable, which might be months away.
But if you were on the 1.9 betas, youâd have found that bug earlier, reported it, and gotten the fix in 1.9.0-beta03 within weeks. The âriskâ of running betas is actually lower than the risk of being stuck on stable with no path to a fix.
This is the reframe: stable doesnât mean âsafest.â It means âoldest code that passed all the quality gates.â The betas contain the same fixes plus additional ones. Theyâve been through the same test suites. They just havenât waited long enough for the calendar to declare them stable.
Once you start adopting betas, you need a system for tracking whatâs beta and whatâs stable. I use Gradle version catalogs, and the key is making the distinction visible.
// gradle/libs.versions.toml
[versions]
# Stable
activity = "1.9.3"
core-ktx = "1.15.0"
lifecycle = "2.8.7"
# Beta â review before each release
compose-bom = "2024.12.01"
compose-foundation = "1.8.0-beta02"
navigation-compose = "2.9.0-beta01"
The comment marker is deliberate. Before every production release, I search libs.versions.toml for beta or alpha. If a stable version has shipped, we upgrade. This takes five minutes during release prep. You can also configure Renovate or Dependabot to flag when a stable version becomes available for a beta dependency.
Donât mix stable and beta versions of tightly coupled libraries. If you upgrade compose-foundation to a beta, make sure compose-ui, compose-runtime, and compose-material3 are on compatible versions from the same Compose BOM. Mismatched Compose versions are a common source of cryptic NoSuchMethodError crashes at runtime.
The key enabler for running betas is testing infrastructure. IMO, if you donât have a test suite you trust, you shouldnât be adopting betas â but you also shouldnât feel safe on stable either.
Upgrade one library at a time. Iâve seen teams bump 15 AndroidX dependencies in one PR and wonder which caused the failure. Upgrade one library at a time, or at least one logical group (all Compose libraries via the BOM). Your CI should run unit tests, integration tests, and screenshot tests on every dependency change. A Compose beta once changed how LazyColumn measured items â technically correct but shifted our list layouts by a few pixels. Screenshot tests caught it immediately.
Test the upgrade path, not just the destination. When you move from beta01 to beta02, run your tests. When stable finally ships, run them again. Occasionally a stable release bundles a last-minute fix that changes behavior slightly from the RC. Itâs rare, but a full test run on the stable upgrade is cheap insurance.
Iâm not suggesting you flip every AndroidX dependency to beta overnight. The honest downside is that you might occasionally hit a newly introduced bug. Itâs rare given Googleâs testing infrastructure, but itâs possible. Gradual adoption is the right approach.
Start with mature, stable-core libraries. Libraries like collection, core, activity, and annotation donât change much between versions. The delta between releases is small, so these are easy wins with almost zero risk.
Move to load-bearing Compose libraries. Compose runtime, foundation, and ui are the ones where bugs actually hurt and fixes matter most. Running betas means you get fixes weeks instead of months after they land. Bigger commitment, but the biggest return.
Keep experimental libraries on stable. Newer libraries still finding their API shape should be evaluated more carefully. The beta API-freeze guarantee only applies once the library has reached beta01 â alphas can still have breaking changes.
When migrating from beta to stable, the transition should be boring. If you were on 1.9.0-beta03 and 1.9.0 stable ships, you bump the version and run your tests. The API surface hasnât changed since beta01 and the stable is identical to the RC, so there should be zero code changes on your side. If you had @OptIn annotations for experimental APIs that graduated to stable, you can remove them â but they wonât break anything if you leave them.
Hereâs something I think is underappreciated: we have a shared responsibility to report bugs upstream. If you find a bug in a beta, file it. Donât assume someone else will. Your app exercises these libraries in ways that Googleâs internal testing doesnât cover. Cash App runs a large codebase with complex usage patterns, and they find real bugs that wouldnât surface in simpler setups.
Reporting bugs in betas is more valuable than reporting them against stable, because the fix can ship in a subsequent beta within weeks. By the time stable rolls around, the bug you found is already fixed â not just for you, but for every AndroidX user. This is the virtuous cycle that makes beta adoption work at ecosystem scale.
IMO, the biggest benefit of running betas is the mindset shift. Instead of passively consuming stable releases and hoping they donât have bugs, you become an active participant in the libraryâs quality. You catch issues earlier, you have a direct path to fixes, and youâre never stuck waiting months with a known problem.
The âstable onlyâ policy feels safe, but itâs a false safety. AndroidX beta01 is production-ready code that has already been tested more thoroughly than most librariesâ stable releases. Give it a try with one or two mature libraries and see for yourself.
Thanks for reading!