AI & Machine LearningMobile Development

Modern Android 16 stack: Compose 1.11, AGP 9.2.0, Firebase

Your Android app usually doesn’t break during the upgrade. It breaks two weeks later, when a harmless-looking CI rebuild pulls a slightly different dependency graph, a preview emulator starts enforcing a new behavior, and somebody realizes your “temporary” AGP pin is now the oldest thing in the repo.

Building a modern Android 16 mobile stack in late June 2026 means lining up around a very specific baseline: Android Studio Quail 1 | 2026.1.1 Patch 2, Android Gradle Plugin 9.2.0, Jetpack Compose April ’26 stable with core modules at 1.11, Android 16 preview SDK, and Firebase Android BoM 34.15.0. Google’s Android Developers latest updates feed puts hard dates on that: Quail 1 | 2026.1.1 Patch 2 is stable on June 26, 2026; AGP 9.2.0 and Firebase Android BoM 34.15.0 both landed on June 16, 2026. This isn’t release-note sightseeing. It’s the floor now.

If your team is still carrying an older AGP, a half-migrated Compose setup, and hand-pinned Firebase libraries, don’t panic. Do get organized. I’d rather see a team spend one disciplined sprint on this stack than spend six months explaining random build failures and Android 16 regressions one ticket at a time.

Android 16 stack baseline: pick the line in the sand

The cleanest way to think about this upgrade is as a baseline reset, not a pile of unrelated version bumps. Android Studio Quail 1 | 2026.1.1 Patch 2 is your tooling anchor. AGP 9.2.0 is your build anchor. Jetpack Compose 1.11 is your UI anchor. Firebase Android BoM 34.15.0 is your services anchor. Android 16 preview SDK is the platform pressure that forces all of them into the same room.

Teams get into trouble when they upgrade one layer in isolation. AGP moves first, but the Gradle wrapper lags. Compose moves, but half the modules still pin artifacts manually. Firebase adopts the BoM in one module while another keeps explicit versions. The build stays green for a while. Then it starts lying.

A large codebase needs one source of truth. In practice, that means a version catalog such as libs.versions.toml, plus a hard rule that build logic reads from it rather than sprinkling versions across module files. A typical mid-2026 modular app might look like this in prose: :app at the edge; :core:design for Compose components and themes; :core:domain for business rules; :core:data for repositories and Firebase adapters; :feature:* modules for screens and flows; thin Android 16-specific adapters close to :app.

That structure matters because Android 16 behavior changes should have a small blast radius. If permission prompts, background execution rules, SDK behavior, or file access rules change, you want to touch boundary code, not every feature module in the app. This is the right default. The opposite design, where platform logic leaks into UI and repositories, turns every target SDK bump into archaeology.

Upgrading to AGP 9.2.0 without detonating CI

AGP 9.2.0 becoming stable on June 16, 2026 is the least glamorous part of this stack, and probably the most important. The Android Gradle Plugin controls enough of the build graph that a casual upgrade is asking for pain. Start in a branch dedicated to build tooling. Keep product changes out of it. Don’t mix feature work with a plugin migration unless you enjoy guessing which fire belongs to which commit.

Move to the plugins DSL first

Google’s “About Android Gradle plugin” documentation shows the modern shape clearly: set the plugin version explicitly with the plugins DSL, using declarations like id("com.android.application") version "9.2.0". If your project still uses the old buildscript block with classpath dependencies, this is a good moment to stop carrying that debt.

plugins {
    id("com.android.application") version "9.2.0" apply false
    id("com.android.library") version "9.2.0" apply false
    kotlin("android") apply false
}

Then wire module plugins in the normal way. Keep the AGP version centralized. Don’t let library modules freelance.

Coordinate the wrapper and build logic

The order matters. Update gradle-wrapper.properties first, then root build logic, then module scripts, then custom tasks. CI should validate each step. The ugly edge case is custom Gradle code that depends on internal AGP APIs. A lot of large Android repositories still have one or two of these. They worked because nobody touched them. AGP 9.2.0 is where they tend to complain.

This is where build scans earn their keep. If your cache hit rate collapses after the migration, or configuration time spikes, don’t guess. Capture the scan, compare before and after, and look for task invalidation patterns. In one common failure mode, generated source tasks start producing slightly different paths, which blows up remote cache reuse across CI agents.

Quail 1 helps here because local and CI behavior are easier to compare when the team aligns on the same IDE family and plugin set. If one developer is still on an older Studio build while the pipeline runs AGP 9.2.0 and Android 16 tools, you’ve created a lab for false positives.

My rule for AGP upgrades is simple: if you can’t explain the build graph, you shouldn’t upgrade it on Friday.

Jetpack Compose 1.11 and Android 16: the UI layer grows up

Jetpack Compose April ’26 is stable for production use, and Google calls out the core Compose modules as version 1.11. Treat that as permission to stop hovering. If you’ve been running a mixed XML and Compose app with uneven dependency versions, this is the moment to normalize it with a BOM-driven model.

Use the Compose BOM as a contract

The Compose BOM isn’t a convenience feature. It’s a safety rail. In a multi-module app, misaligned UI artifacts can produce exactly the kind of failures teams hate most: weird runtime behavior, subtle recomposition issues, crashes, and branch-specific or variant-specific surprises. Pinning the BOM makes those problems less likely and easier to reason about.

dependencies {
    implementation(platform(libs.androidx.compose.bom))
    androidTestImplementation(platform(libs.androidx.compose.bom))

    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.material3:material3")
    implementation("androidx.compose.ui:ui-tooling-preview")
    debugImplementation("androidx.compose.ui:ui-tooling")
}

Aligned stone and resin layers forming an abstract upgrade path

Modernization is controlled alignment: tooling, UI, platform, and services moving in step.

Version catalogs make this cleaner, but the bigger point is process: the BOM version should live beside AGP, Kotlin, and Firebase BoM in the same controlled file. CI should fail if modules attempt to pin Compose artifacts directly. This sounds strict because it is. I’d rather reject a rogue dependency at review time than debug a rendering issue in staging.

Mixed XML and Compose is still a valid state

Not every mature Android app should go all-in on Compose overnight. A lot of teams are still carrying XML-heavy navigation, old fragments, or design systems that aren’t ready for a full rewrite. Fine. Compose 1.11 works well as a disciplined insertion point: new screens in Compose, old screens wrapped or bridged, shared state managed in feature modules, and UI behavior kept out of views as much as possible.

A useful module split is to keep reusable composables in :core:design, screen-level UI and state holders in :feature:checkout or similar, and platform-specific integrations at the app boundary. That leaves your Compose code mostly insulated from Android 16 behavior changes. If a permission flow changes, you update the launcher and state contract, not every composable that depends on the result.

The Kotlin Multiplatform angle is real, but optional. Android Developers lists compose among multiplatform-supported libraries, with versions like 1.11.3 stable and 1.12.0-beta01 available as of June 17, 2026. For an Android-first team, I wouldn’t force KMP into the production app just because the tooling is tempting. It’s fine for a prototype, not for a fleet. A better move is a sandbox module such as :core:ui shared between Android and a tiny internal desktop app. Learn the seams. Keep the blast radius small.

Firebase BoM 34.15.0 and service-layer sanity

Firebase Android BoM 34.15.0 landed on June 16, 2026. If your project still hardcodes versions for Authentication, Firestore, Analytics, Crashlytics, or friends module by module, this upgrade is the perfect excuse to stop doing that. Manual pinning looks precise right up until two libraries drift apart and you discover the mismatch through a flaky analytics event or a startup regression.

dependencies {
    implementation(platform("com.google.firebase:firebase-bom:34.15.0"))

    implementation("com.google.firebase:firebase-analytics")
    implementation("com.google.firebase:firebase-crashlytics")
    implementation("com.google.firebase:firebase-auth")
    implementation("com.google.firebase:firebase-firestore")
}

The Firebase Android SDK release notes page maps BoM 34.15.0 to compatible SDK versions. You don’t need to copy that whole matrix into your repo. You do need to trust one version source and remove the others. Keep Firebase integration code in data-layer adapters, expose repository interfaces upward, and make sure analytics and crash-reporting calls are testable without the SDK present.

CI should run a smoke suite after this change. Not a giant end-to-end marathon. A narrow, purposeful set of checks: app startup, authentication handoff, one analytics event, one controlled crash path in a non-production flavor, one Firestore read/write path against staging, and verification that Crashlytics and Analytics stay isolated from production data during validation. Use separate staging projects in Firebase when validating new SDK versions. Polluting production metrics during an SDK migration is the wrong kind of excitement.

Android 16 behavior changes: test them like failures, not trivia

Google’s Android 16 preparation checklist is more useful than many teams admit. Set up a runtime environment with either a flashed Google Pixel device or the emulator. Set up Android Studio with the Android 16 SDK and tools. Review behavior changes for all apps and for apps targeting Android 16. Test critical flows. Toggle behavior changes at runtime. Then update the app to target Android 16 and roll out to beta users. That sequence is practical. Follow it.

In Quail 1, create a dedicated Android 16 emulator image and keep it around as a named test target. Don’t reuse a random device profile somebody made three previews ago. Pair it with an internal QA build variant that exposes feature flags tied to Android 16-sensitive code paths: background work, notifications, permission prompts, file access, deep-link handling, and any SDK-dependent flows your app actually relies on.

interface PlatformBehaviorToggles {
    val useAndroid16PermissionFlow: Boolean
    val useAndroid16BackgroundWorkRules: Boolean
    val useAndroid16NotificationBehavior: Boolean
    val useAndroid16FileAccessRules: Boolean
}

Then wire instrumented tests around those toggles. The point isn’t to simulate the whole OS. It’s to isolate app behavior while the team still controls the timing. If a critical flow fails only when a specific Android 16 behavior is enabled, you’ve learned something actionable before the targetSdkVersion move turns it into a production deadline.

Here’s the opinionated part. Teams that “wait for Android 16 to settle” are usually not reducing risk; they’re deferring discovery. That’s a poor strategy for any serious mobile app. Preview periods exist so you can surface the awkward bugs while your architecture still has room to breathe.

What the CI/CD pipeline should enforce

A modern pipeline for this stack should do more than compile. It should validate the baseline. Pin the Gradle wrapper in source control. Build with the same JDK family across local and CI environments. Run unit tests on every branch, Compose screenshot or rendering checks where applicable, instrumented Android 16 tests on a scheduled cadence or pre-release branch, and dependency policy checks that catch direct version pins. Publish build scans and keep historical comparisons. When AGP 9.2.0 or Compose 1.11 changes task behavior, those comparisons become your memory.

Release trains get calmer when the stack is explicit. A single catalog version bump can fan out through :app, :core:data, and :feature:* modules in a predictable way. Hidden version drift can’t. That’s the real payoff here: not novelty, not badge value, just fewer unknowns in production engineering.

The baseline is clear. The question is whether your repo is.

Late June 2026 gives Android teams a rare gift: a clean technical baseline with dates, versions, and documentation that line up. Android Studio Quail 1 | 2026.1.1 Patch 2. AGP 9.2.0. Jetpack Compose April ’26 stable at 1.11. Firebase Android BoM 34.15.0. Android 16 preview SDK and a concrete preparation checklist. You don’t need to upgrade everything in one reckless afternoon. You do need a plan that treats these pieces as one stack.

Bright modern corridor with repeating openings and subtle navy and gold accents

A well-updated stack narrows uncertainty and opens a clearer path to the next platform cycle.

So ask the uncomfortable question. If you targeted Android 16 next month, would your app be blocked by architecture, by build logic, by CI, or by plain uncertainty? That answer tells you what to fix first.