On July 15, three Jetpack XR libraries moved simultaneously from alpha to beta: xr.arcore, xr.scenecore, and xr.runtime, all at version 1.0.0-beta01. If you've been building on the alpha releases, this transition matters more than a typical point bump. In the AndroidX versioning model, beta means the API surface is mostly frozen — the library team is no longer expected to make breaking changes before stable. Alpha was where the design was still in flux. Beta is where you start committing.
The catch: getting from alpha to beta01 required the team to make a handful of breaking changes that they'd been deferring. Some are pure renames. One is a fundamental signature change that touches any code path where you create a session. If you've got a spatial app already running on an alpha build, you're going to hit all of these when you upgrade.
Here's what actually changed, why each decision makes sense, and how to migrate.
1. Session.create Is Now a Suspend Function
This is the change that's going to bite the most code. Across all three libraries — arcore, scenecore, and runtime — Session.create was previously a regular blocking call. In beta01, it's been converted to a suspend function.
The reason is straightforward once you think about it. Creating an XR session involves hardware initialization — communicating with the device's spatial tracking subsystem, loading tracking models, establishing connections to the runtime. None of that can complete instantaneously, and doing it on the calling thread risked ANRs if called on the main thread or silent delays if called on a background thread with no visibility into what was happening. Making it suspend forces callers to handle it asynchronously from the start, which is where it should have been all along.
What this looks like in practice:
// Alpha: regular function call
class XrActivity : AppCompatActivity() {
private lateinit var session: Session
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
session = Session.create(this) // blocking, could stall main thread
setupSpatialScene(session)
}
}
// Beta01: suspend function — call from a coroutine scope
class XrActivity : AppCompatActivity() {
private var session: Session? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
session = Session.create(this@XrActivity)
setupSpatialScene(session!!)
}
}
}
If you have a single-activity architecture with the session injected via a ViewModel, the cleanest pattern is to create the session inside a viewModelScope.launch block and expose it as a StateFlow<Session?>. The UI then collects that flow and defers spatial setup until the session is non-null. That pattern also handles the case where session creation fails — you get a proper coroutine exception you can catch and surface in the UI, rather than a hard crash on whatever thread was calling the old API.
Also removed in beta01: the ExperimentalXrDeviceLifecycleApi opt-in annotation was removed from xr.runtime. Any @OptIn(ExperimentalXrDeviceLifecycleApi::class) annotations in your code will now generate a warning (unresolved reference). Delete them — the APIs they guarded are no longer experimental.
2. AnchorEntity Is Now AnchorSpace
The entity hierarchy in Jetpack XR got a conceptual cleanup. In alpha, you had AnchorEntity — a spatial anchor that could serve as a coordinate reference for placing content relative to the real world. The name was misleading: an anchor isn't really an entity in the scene graph sense; it's a space, a coordinate frame that the system controls and updates as tracking improves.
Beta01 renames it to AnchorSpace and introduces a shared base class: both ActivitySpace and AnchorSpace now extend a common SpaceEntity. The hierarchy is cleaner — SpaceEntity captures the behavior common to both (the system, not your code, controls where they sit in the world), while ActivitySpace and AnchorSpace carry the specific semantics of their respective types.
// Alpha
val anchor: AnchorEntity = session.createAnchorEntity(hitResult.hitPose)
spatialPanel.setParent(anchor)
// Beta01
val anchor: AnchorSpace = session.createAnchorSpace(hitResult.hitPose)
spatialPanel.setParent(anchor)
// The base type is now SpaceEntity if you need to handle both
fun positionRelativeTo(space: SpaceEntity) {
spatialPanel.setParent(space)
}
This is a mechanical rename at the call site. Your IDE should find every occurrence in a single find-replace pass. The behavior is unchanged; only the type names differ.
3. Movable and Resizable Got a Policy API
In alpha, attaching move and resize behavior to spatial panels involved two separate modifiers — transformingMovable and movable — which didn't compose cleanly and made it awkward to express different policy intent. Beta01 replaces this with explicit policy parameters on a unified modifier.
transformingMovable and the old movable overload are now deprecated. The replacement is a movable overload that takes a movePolicy parameter. Similarly, resizable now takes a ResizePolicy instead of the previous two-modifier approach.
// Alpha: separate modifiers for different move behaviors
Modifier
.transformingMovable(moveExecutor)
.movable()
// Beta01: single modifier with explicit policy
Modifier.movable(movePolicy = movePolicy)
// Resizable also moved to a policy model
Modifier.resizable(resizePolicy = resizePolicy)
The motivation is cleaner intent expression. Different MovePolicy and ResizePolicy values let you declare whether the system drives the behavior, the user drives it, or some combination — without layering modifiers that the framework then has to reconcile. It also makes it easier to change behavior at runtime by re-composing with a different policy value rather than swapping which modifier is applied.
Also removed: MovableComponent.createTrackingMovable() factory method in xr.scenecore. This was the programmatic path for ARCore Trackable-based movement. You'll need to use the modifier-based approach instead, which integrates more naturally with the Compose for XR component model.
4. TrackingState.TRACKING_DEGRADED Is Gone
Previously, ARCore for Jetpack XR exposed a TRACKING_DEGRADED state to indicate that tracking was still technically active but producing lower-confidence results — typically due to poor lighting, fast camera movement, or textureless surfaces. In beta01 this state has been removed from the public API. Conditions that previously mapped to TRACKING_DEGRADED now map to PAUSED instead.
The change reflects a design decision about what developers should actually do with that information. In practice, the response to "tracking degraded" should usually be the same as the response to "tracking paused": stop placing content, show a recovery UI, wait. Exposing a distinct degraded state created a third branch in tracking logic that most apps either ignored or handled identically to paused. Collapsing them simplifies the state machine for everyone.
// Alpha: three-state check
when (trackable.trackingState) {
TrackingState.TRACKING -> showAnchoredContent()
TrackingState.TRACKING_DEGRADED -> showDegradedWarning()
TrackingState.PAUSED -> showTrackingLostUI()
}
// Beta01: two-state check
when (trackable.trackingState) {
TrackingState.TRACKING -> showAnchoredContent()
TrackingState.PAUSED -> showTrackingLostUI()
}
If your app was showing meaningfully different UI for the degraded state, revisit whether that distinction was actually helping users. If you were handling it identically to paused — which is the likely case — this simplification removes a dead code path.
5. New: Rounded Corners on SurfaceEntity Canvases
Not everything in beta01 is about migration. SurfaceEntity.Shape.Quad in xr.scenecore now accepts an optional cornerRadius parameter. Previously, spatial panels rendered with sharp corners — usable, but visually out of place next to the rounded, card-like aesthetic most spatial UIs aim for. The new parameter lets you match the corner radius to whatever design language your app uses.
val panel = session.createSurfaceEntity(
SurfaceEntity.Config(
shape = SurfaceEntity.Shape.Quad(
width = 0.4f,
height = 0.3f,
cornerRadius = 0.02f // 2cm radius on a 40x30cm panel
)
)
)
It's a small API addition, but if you've been placing panels in XR scenes you already know that sharp-cornered quads look wrong next to rounded system panels. This closes that gap.
6. Testing APIs Now Cover Spatial Audio and GltfAnimation
Beta01 adds a set of public test APIs that the alpha release was missing. SpatialAudioTrackBuilderTester, SpatialAudioTrackTester, and SoundEffectPoolComponentTester are now part of the scenecore testing module, exposable via SceneCoreTestRule.getTester() overloads. On the runtime side, SessionTestRule arrived in the runtime-testing artifact.
These matter because testing spatial audio behavior has been essentially undoable in automated tests without them — you either skip audio tests entirely or write integration tests that require a real device. The tester APIs let you verify that spatial audio tracks are configured correctly, pooled sound effects behave as expected under test conditions, and session lifecycle transitions work the way your code assumes.
GltfAnimation and SpatialGltfModelAnimation also arrived as experimental APIs in scenecore, giving you programmatic control over the animation state of GLTF models in your spatial scene. They're gated behind their own experimental opt-in for now.
How to Approach the Migration
The scope here is narrower than a full API revision. Most of the changes are mechanical: a class rename, an annotation removal, a modifier consolidation, and a suspend conversion that your coroutine compiler errors will identify immediately. What I'd do in order:
- Bump all three libraries to 1.0.0-beta01 simultaneously. They were released together and depend on each other — partial updates will produce confusing resolution errors.
- Let the compiler find the breaking call sites.
AnchorEntityreferences, removed experimental annotations, and non-suspend callers ofSession.createall produce compile errors. Work through them before running the app. - Fix Session.create first. It's the change most likely to introduce runtime issues if handled carelessly. Move it into a coroutine scope, expose the result via StateFlow, and verify the lifecycle is correct before moving on to the renames.
- Replace tracking state branches. Search for
TRACKING_DEGRADEDand either merge that branch intoPAUSEDhandling or delete it. - Update movable/resizable modifier usage. The old forms are deprecated, not removed, in beta01 — your code still compiles with them for now. But the deprecation warnings are real and they will be removed before stable. Better to migrate now than carry deprecated modifier usage into a production spatial app.
Also worth checking: ProjectedPermissionsResultContract was deprecated in xr.arcore beta01. If you're requesting projected-mode permissions through it, watch for the replacement API in upcoming beta releases — the deprecation suggests a replacement is coming before stable.
One thing this beta milestone signals clearly: the Jetpack XR team considers the core session, entity hierarchy, and movement model design to be settled. What you migrate to today is much closer to the stable API surface than what you had in alpha. If spatial computing is on your roadmap — and for apps that deal with spatial audio, AR content, or premium hardware experiences, it probably should be — beta01 is a reasonable point to invest seriously in the platform rather than treating it as pre-production experimentation.
The math on Android XR devices entering the market is also starting to matter. More devices means more user traffic, which means spatial features shift from a differentiator to table stakes faster than most development teams expect. I'd rather spend time migrating to beta now and be ready, than spend it firefighting a rushed migration when a launch deadline is in sight.
No comments yet. Be the first to leave one!