XR 10 min read

Android XR Developer Preview 4: What the Jetpack XR SDK Actually Means for Android Developers

For most of the past year, Android XR felt like something you'd follow from a safe distance — interesting announcements at Google I/O, impressive headset demos, but nothing that touched the day-to-day work of shipping a production Android app. That changed this month. ARCore for Jetpack XR hit 1.0.0-alpha15 on June 17, bundling a Geospatial API overhaul, a brand-new QR Code detection API, and a set of hand tracking changes that signal the SDK is solidifying toward a stable release. Google also published a full mixed-reality tour guide sample built with the Geospatial API and Gemini just three days ago.

I'm not building an XR app tomorrow. You're probably not either. But after spending time with the Jetpack XR SDK, I think there's a better framing than "wait and see." If you've been doing Android for a few years, the SDK is a lot more familiar than the XR branding suggests — and the architecture decisions Google made mean that much of what you already know transfers directly.

alpha15
ARCore for Jetpack XR latest (June 17, 2026)
DP4
Android XR Developer Preview 4
4
Form factors: headsets, wired glasses, audio glasses, display glasses

The Four Form Factors, and Why They Matter for Architecture

Android XR isn't a single device type. The SDK targets four distinct hardware categories:

The architectural implication is that you can't write one codebase that assumes a single output surface. The SDK handles this through a layered approach: some libraries work across all form factors, some are scoped to headsets and wired glasses only, and some are specific to the glasses tier. You query capabilities at runtime rather than branching on device model strings — the same pattern we've been using for phone/tablet adaptivity, applied to an entirely new dimension.

The SDK Layers: What Each One Does

The Jetpack XR SDK isn't a monolith. It ships as six separate libraries, and understanding what each one handles saves you from conflating concerns that need to stay separate.

ARCore for Jetpack XR

Dependency: androidx.xr.arcore:arcore:1.0.0-alpha15

This is where the world-understanding lives. Plane detection with semantic labels (floor, wall, tabletop), persistent anchors, motion tracking, hand tracking, and — as of alpha12 — image marker tracking via AugmentedImage. The session lifecycle should feel immediately familiar to anyone who's used ARCore on mobile, though the API has been modernized to Kotlin coroutines and async patterns throughout:

// Create a session — returns a Result, not a nullable
val session = Session.create(context).getOrThrow()

session.configure(
    Config(
        planeFindingMode = Config.PlaneFindingMode.HORIZONTAL_AND_VERTICAL,
        geospatialMode = GeospatialMode.SPATIAL,  // replaces VPS_AND_GPS as of alpha14
    )
)

// Plane updates come as a Flow
session.planes
    .collect { planes ->
        planes
            .filter { it.trackingState == TrackingState.TRACKING }
            .forEach { plane ->
                when (plane.label) {
                    PlaneLabel.FLOOR   -> anchorToFloor(plane)
                    PlaneLabel.TABLE   -> anchorToTable(plane)
                    else               -> { /* skip */ }
                }
            }
    }

The rename from Plane.Label to PlaneLabel (and similar flattening of Hand.HandSide to HandSide, Plane.Type to PlaneType) landed in alpha13 and makes the API feel more idiomatic Kotlin — no nested sealed class path to navigate when you just want to pattern-match on a plane label.

Jetpack Compose for XR

Dependency: androidx.xr.compose:compose:1.0.0-alpha15

If you write Compose today, you already know most of the mental model here. The key concept is the subspace — the three-dimensional region around the user where XR content lives — versus the standard 2D Compose surface. You place content in the subspace using dedicated composables:

@Composable
fun MyXrApp() {
    // Regular 2D composable — works as-is on XR
    Box(Modifier.fillMaxSize()) {
        Text("Standard 2D content")
    }

    // Subspace: opt into the 3D layer
    Subspace {
        SpatialPanel(
            modifier = SubspaceModifier
                .width(800.dp)
                .height(600.dp)
                .resizable()          // system-managed resize in alpha15
                .movable()            // user can grab and reposition
        ) {
            // Any Compose content goes here — your existing screens just work
            MyExistingScreen()
        }

        // glTF model floating next to the panel
        SpatialGltfModel(
            state = rememberSpatialGltfModelState(
                source = SpatialGltfModelSource.fromResource(R.raw.my_model)
            )
        )
    }
}

The SpatialGltfModelSource.fromResource API landed in alpha15 — before that, loading models from Android resources required more ceremony. Small quality-of-life change, but the kind that tells you the team is polishing toward stability.

Jetpack SceneCore

Dependency: androidx.xr.scenecore:scenecore:1.0.0-alpha15

The lower-level scene graph. If Compose for XR is the declarative layer, SceneCore is the imperative one beneath it. You use it directly when you need fine-grained control over entity placement, spatial audio sources, or when you're working with game engines that bypass Compose entirely. For most app developers, Compose for XR abstracts over SceneCore — you'd reach for it the way you'd reach for Canvas in traditional Android development: rarely, and when you genuinely need the control.

Jetpack Compose Glimmer + Jetpack Projected

These two libraries target the glasses tier specifically. Glimmer is a Compose subset optimized for the constraints of a small display glass — simplified color palette, no ripple effects (the display can't handle them cleanly), voice interaction support. Projected handles the phone-to-glasses communication channel and gives you access to glasses camera and display hardware via standard Android permission APIs. If you're only interested in headset development, you can ignore both of these for now.

The Geospatial API Overhaul

The biggest API evolution across recent alphas is the Geospatial mode redesign. In alpha13 and earlier, you could enable GeospatialMode.VPS_AND_GPS to get location-aware anchors — anchors placed relative to real-world geographic coordinates rather than just relative to where the session started. Useful for building experiences that persist across device restarts, or that multiple users can see in the same physical location.

Alpha14 deprecated VPS_AND_GPS in favor of two cleaner modes:

The practical split: if you're building something like a location-anchored tour guide (which is exactly what Google's June 22nd sample app does — blending Geospatial anchors with Gemini for contextual commentary), you want SPATIAL. If you're building an in-room experience where you just need objects to stay put relative to where the user is standing, INERTIAL gives you that at lower battery cost.

session.configure(
    Config(
        // For world-anchored content that survives device restart or multi-user scenarios
        geospatialMode = GeospatialMode.SPATIAL,

        // Or: for in-room-only, no GPS dependency, lower power
        // geospatialMode = GeospatialMode.INERTIAL,
    )
)

// Check geospatial state before placing anchors
session.earth?.let { earth ->
    if (earth.trackingState == TrackingState.TRACKING &&
        earth.earthState == GeospatialState.ENABLED) {
        val anchor = earth.createAnchor(
            latitude  = 28.6139,
            longitude = 77.2090,
            altitude  = 216.0,
            eastUpSouthQuaternion = floatArrayOf(0f, 0f, 0f, 1f)
        )
    }
}

Worth noting: VPS accuracy depends on Google's Street View imagery for the area. In India — where a large portion of Nodat's user base is — VPS coverage is solid in major cities but patchy in tier-2 and tier-3 cities. Design location-anchored features with graceful fallback to INERTIAL mode when GeospatialState isn't ENABLED.

The QR Code API (alpha15)

A small but genuinely useful addition in the June 17 release: QrCode tracking. The API follows the same pattern as AugmentedImage (which landed in alpha12 for arbitrary image marker tracking), but is optimized for the specific geometry and error-correction structure of QR codes. The practical use is obvious — scan-to-anchor, letting a user point their headset at a QR code on a physical object or location to trigger an XR experience tied to that exact spot:

// Configure QR tracking in the session config
session.configure(
    Config(qrCodeMode = Config.QrCodeMode.ENABLED)
)

// React to detected QR codes
session.qrCodes
    .collect { codes ->
        codes
            .filter { it.trackingState == TrackingState.TRACKING }
            .forEach { qr ->
                val payload = qr.rawValue  // decoded string content
                val anchor  = qr.createAnchor()  // anchor at the QR's physical location
                handleQrTrigger(payload, anchor)
            }
    }

This is the kind of API that becomes a standard UX pattern quickly. Every enterprise XR use case — warehouse inventory, equipment maintenance, onboarding guides — has a "scan this to activate" step. Having it as a first-class SDK primitive instead of a CV library integration reduces a meaningful amount of integration work.

What Your Existing Android Skills Cover

I've spent enough time in the XR samples and API references to have an opinion on this: the learning curve is smaller than the branding suggests, if you've been doing Android for a few years.

What's genuinely new is the spatial reasoning: thinking in three dimensions for layout, understanding tracking states and what to do when they degrade, and designing UX for users who are physically in a space rather than sitting at a phone. The SDK can't teach you that part. But it gets out of the way and lets you focus on it, which is the right call.

Game engine support expanded at Google I/O 2026: Unity, Unreal Engine (with a native VR template), and Godot (via the OpenXR Vendors plugin) are all now supported. If you're an Android developer who's never touched a game engine, you don't need to — the Jetpack XR SDK is the native path and it's the one that integrates cleanly with everything you already use.

Should You Start Building Now?

The honest answer is: it depends on what "start" means.

If "start" means prototyping, learning the API shape, and building intuition for spatial UI — yes, today is a fine time. The emulator support in Android Studio is solid, the AVD for XR headsets is available without needing physical hardware, and the SDK has reached the point where the API surfaces are stable enough that prototypes you write today will need only targeted updates rather than wholesale rewrites when stable hits.

If "start" means shipping to production — that's a different answer. This is still a Developer Preview. The @Experimental annotations scattered across both Compose for XR and ARCore for Jetpack XR are accurate warnings: APIs have changed significantly between alphas (the alpha13 enum renaming was not a small diff), and they'll likely change again before 1.0.0 stable. Building production architecture on top of APIs that ship breaking changes between monthly releases is a bet I wouldn't take right now.

The right move for most Android developers is the same one I'm taking: follow the release notes, run the samples, maybe port one non-critical internal screen to a SpatialPanel to understand how the spatial model works, and be positioned to move fast when the stable release drops. The teams who understand this SDK's architecture now are the ones who'll be first to market when hardware availability makes XR apps a real business decision.

Staying Current

The release cadence has been roughly monthly since December 2024, and the changelogs in the Jetpack XR AndroidX release notes are genuinely informative — more so than most library changelogs. They explain the why behind renames and removals, not just the what. Worth subscribing to:

If you're already tracking Compose and AndroidX releases (and at this point in 2026, if you're shipping Compose apps in production, you should be), adding three more release note feeds is low overhead for keeping your XR knowledge current.

Comments 0

No comments yet. Be the first to leave one!

Leave a comment