Reproduction repository (clone → npm install → npx expo run:android):
https://github.com/lsadsad/rive-android-nested-pg-repro — contains both .riv files, the probe component, and a README with the observed logs. Verified end-to-end on 2026-08-23: fresh install from public npm, 38 s build, both probes reproduce on an API 36 emulator while iOS passes.
Summary
A Property Group keyed on a nested item artboard, instanced through an ArtboardComponentList bound to a view-model list, never advances on Android. The file loads, the view model binds, the list resolves, and property writes land and read back correctly — but the group's animated property stays at its initial value forever. The identical JS code and identical .riv binaries work correctly on iOS and on web (@rive-app/canvas).
The failure is completely silent: no error, no warning, no onError callback — the animation simply never runs. On a production screen this presents as "loads but sits there."
Environment
|
|
@rive-app/react-native |
0.4.19 |
react-native-nitro-modules |
0.35.10 |
rive-android |
11.7.2 (as pinned by the package's runtimeVersions.android) |
RiveRuntime (iOS) |
6.21.1 (pinned by runtimeVersions.ios, verified in Podfile.lock) |
| React Native |
0.85.3 (Expo SDK 56.0.18, new architecture) |
| Android |
emulator, API 36 (Android 16), arm64 — FAILS |
| iOS |
simulator, iPhone 15 Pro — works |
| Web reference |
@rive-app/canvas 2.39.1 — works |
The .riv structure (two binaries, 861 B and 772 B)
Parent artboard PGNestProbe, state machine PGNestSM, view model with a list property items (3 items). The list drives an ArtboardComponentList; each item is a nested item artboard whose view model has:
progress (number) — animated 0 → 100 over 2000 ms by a Property Group keyed on the nested item artboard
phase (number) — gate input (main binary only)
Two variants isolate conditionality:
- Gated (
pg_nest_probe.riv, md5 d96f10b1): item state machine has ItemIdle --[phase >= 1]--> ItemRun. The host writes phase := 1 per item, staggered 700 ms apart, and polls progress per item.
- Autoplay discriminator (
pg_nest_probe_pregate.riv, md5 002f3738): built before the gate existed — no phase property, no ItemIdle state, no condition anywhere. Each item artboard runs entry → ItemRun and should simply play.
Reproduction (minimal shape)
const { riveFile } = useRiveFile(require('./pg_nest_probe.riv'));
const { instance } = useViewModelInstance(riveFile, { async: true });
const { length, getInstanceAt } = useRiveList('items', instance);
// capture the 3 item instances once length resolves ...
const { value: p0 } = useRiveNumber('progress', items[0]); // per-item read
const { value: ph0, setValue: setPh0 } = useRiveNumber('phase', items[0]); // per-item write
// render
<RiveView file={riveFile} dataBind={instance}
artboardName="PGNestProbe" stateMachineName="PGNestSM" autoPlay />
// then: setPh0(1) (staggered per item), poll p0/p1/p2 every ~250 ms
The full probe component (single file, includes the verdict harness that produced the logs below) is App.tsx in the repro repository above.
Expected
progress ramps 0 → 100 per item; with the staggered gate writes the three items diverge (per-instance state). This is exactly what happens on iOS and web.
Actual
iOS — works (same day, same code, same binaries)
[PGNestNitro] list 'items' length=3; captured 3 instances
[PGNestNitro] phase[0] := 1
[PGNestNitro] t=254ms [10.83, 0, 0]
[PGNestNitro] phase[1] := 1
[PGNestNitro] t=786ms [37.50, 2.50, 0]
[PGNestNitro] phase[2] := 1
[PGNestNitro] t=1554ms [75.83, 40.83, 5.83]
[PGNestNitro] divergence read: [79.17, 43.33, 8.33] ← three distinct values, staggered as written
[PGNestNitro] t=3654ms [100, 100, 100]
Android — gated binary: writes land, nothing advances
[PGNestNitro] list 'items' length=3; captured 3 instances
[PGNestNitro] phase[0] := 1
[PGNestNitro] t=274ms [0,0,0]
[PGNestNitro] phase[1] := 1
[PGNestNitro] phase[2] := 1
[PGNestNitro] t=1605ms [0,0,0]
...
[PGNestNitro] t=4787ms [0,0,0] ← 4.6 s, zero movement
phase reads back [1,1,1] ← the writes DID land
progress reads are real zeros (values resolve; they are not undefined/null), and phase reads back 1/1/1 — so binding, list traversal, per-item addressing, writes, and reads all work. Only the advance is missing.
Android — autoplay discriminator: eliminates conditions entirely
[Pregate] vm: loading=false instance=ready err=null
[Pregate] list 'items' length=3; captured 3
[Pregate] t=243ms [0,0,0]
...
[Pregate] t=3053ms [0,0,0]
VERDICT: progress never moved (spread 0.00) on an AUTOPLAYING build with
no condition anywhere.
With no gate, no condition, and no host writes at all, the nested group still never advances on Android. This rules out "the transition condition doesn't resolve against a list-item instance" — the group itself does not advance.
Ruled out
- Stale install / stale native runtime — single canonical install verified before each run; fresh
expo run:android build.
- Asset drift — md5-verified binaries, served via Metro on both platforms.
- Probe/verdict artifact — the harness treats unresolved reads as INCONCLUSIVE, never as zeros; the Android zeros are resolved readings. The same harness produced PASS on iOS the same day.
- JS-layer difference — identical component code on both platforms; the divergence is below the JS bridge.
Notes
- Everything around the advance works on Android:
RiveFile load, useViewModelInstance, useRiveList + getInstanceAt, per-item useRiveNumber reads and writes (with listeners firing for phase).
- The failure mode is the dangerous kind: silent. If a runtime can't advance nested Property Groups in an ArtboardComponentList, an error or console warning at bind time would save integrators days.
- Everything needed to reproduce is in https://github.com/lsadsad/rive-android-nested-pg-repro; happy to provide additional logs or run variants on request.
Reproduction repository (clone →
npm install→npx expo run:android):https://github.com/lsadsad/rive-android-nested-pg-repro — contains both
.rivfiles, the probe component, and a README with the observed logs. Verified end-to-end on 2026-08-23: fresh install from public npm, 38 s build, both probes reproduce on an API 36 emulator while iOS passes.Summary
A Property Group keyed on a nested item artboard, instanced through an ArtboardComponentList bound to a view-model list, never advances on Android. The file loads, the view model binds, the list resolves, and property writes land and read back correctly — but the group's animated property stays at its initial value forever. The identical JS code and identical
.rivbinaries work correctly on iOS and on web (@rive-app/canvas).The failure is completely silent: no error, no warning, no
onErrorcallback — the animation simply never runs. On a production screen this presents as "loads but sits there."Environment
@rive-app/react-nativereact-native-nitro-modulesrive-androidruntimeVersions.android)RiveRuntime(iOS)runtimeVersions.ios, verified in Podfile.lock)@rive-app/canvas2.39.1 — worksThe .riv structure (two binaries, 861 B and 772 B)
Parent artboard
PGNestProbe, state machinePGNestSM, view model with a list propertyitems(3 items). The list drives an ArtboardComponentList; each item is a nested item artboard whose view model has:progress(number) — animated 0 → 100 over 2000 ms by a Property Group keyed on the nested item artboardphase(number) — gate input (main binary only)Two variants isolate conditionality:
pg_nest_probe.riv, md5d96f10b1): item state machine hasItemIdle --[phase >= 1]--> ItemRun. The host writesphase := 1per item, staggered 700 ms apart, and pollsprogressper item.pg_nest_probe_pregate.riv, md5002f3738): built before the gate existed — nophaseproperty, noItemIdlestate, no condition anywhere. Each item artboard runs entry →ItemRunand should simply play.Reproduction (minimal shape)
The full probe component (single file, includes the verdict harness that produced the logs below) is
App.tsxin the repro repository above.Expected
progressramps 0 → 100 per item; with the staggered gate writes the three items diverge (per-instance state). This is exactly what happens on iOS and web.Actual
iOS — works (same day, same code, same binaries)
Android — gated binary: writes land, nothing advances
progressreads are real zeros (values resolve; they are not undefined/null), andphasereads back1/1/1— so binding, list traversal, per-item addressing, writes, and reads all work. Only the advance is missing.Android — autoplay discriminator: eliminates conditions entirely
With no gate, no condition, and no host writes at all, the nested group still never advances on Android. This rules out "the transition condition doesn't resolve against a list-item instance" — the group itself does not advance.
Ruled out
expo run:androidbuild.Notes
RiveFileload,useViewModelInstance,useRiveList+getInstanceAt, per-itemuseRiveNumberreads and writes (with listeners firing forphase).