Environment
- Flutter web (both DDC dev builds and wasm),
rive: 0.14.9, rive_native: 0.1.9 (wasm artifact @rive-app/flutter-native-wasm@42.0.0)
- Reproduced on Chrome desktop with mouse and with touch input
- Only verified on web so far — we have not ruled out other platforms; the suspect runtime paths are shared, so native targets may be affected too
Setup
- A root artboard whose view model has an artboard property ("slot").
- At runtime we bind a fresh canvas artboard into it:
final vm = file.defaultArtboardViewModel(canvasArtboard)!.createInstance();
final bindable = file.artboardToBind('canvas', viewModelInstance: vm);
rootVm.artboard('slot')!.value = bindable;
- The canvas artboard contains a
LayoutComponent with an ArtboardComponentList of tile artboards. Each tile's state machine has native pointer listeners (Pointer Down / Pointer Enter) that fire view-model triggers.
- The list is populated by a Luau script that reacts to a VM number (
tileNumber).
Actual behavior
If the slot assignment and the list population run in the same synchronous batch (e.g. inside a setup call chain executed from a timer/Future), the mounted content renders perfectly and accepts VM writes (colors, etc.), but its tile pointer listeners never fire — permanently. Hit-testing silently skips the content forever, no matter how much time passes or how many pointer events arrive later. Listeners belonging to the root artboard's own state machine keep working the whole time.
Expected behavior
Programmatic/deferred structural mutations (artboard-property rebinds + component-list population) should converge for hit-testing the same way they do for rendering.
What does NOT fix it (all tried)
- Explicit
stateMachine.advanceAndApply(0) after every structural boundary (slot assign, tileNumber set, per-item color writes) — even though it internally runs up to 5 update passes.
- Synthesizing pointer events through
RiveWidgetController.pointerEvent (hover/down/up at a neutral position) followed by zero-dt advances.
- Waiting arbitrarily long (seconds) between the mutations and the first user interaction.
Workaround that DOES fix it
Splitting the mutation into two separately scheduled steps outside the setup batch:
- assign the (still empty) canvas bindable to the slot;
- in a later scheduled task, set
tileNumber (script populates the list) and write the colors;
with advanceAndApply(0) after each boundary. With that separation the same native listeners fire reliably — mouse, drag and touch, across many rounds, including re-populating a live canvas (changing tileNumber on an already-mounted canvas).
Additional data points
- Mutations executed inside a real Flutter pointer callback (
Listener.onPointerDown) always converge — consistent with the special-cased stateMachine.advanceAndApply(0) for down/up/cancel in RiveWidgetPainter.pointerEvent (rive 0.14.9 widget_controller.dart) — while identical call bodies executed from timers never converge, even with the same explicit advance calls.
- Likely related runtime paths:
StateMachineInstance hit-testing skips component-list items whose worldToLocal is unavailable (state_machine_instance.cpp ~line 1060 continue), and an artboard-property assignment only marks binding dirt (viewmodel_instance_artboard.cpp:24) with the actual mount deferred to updatePass.
- Items added to the
ArtboardComponentList from Dart (list.add(...) instead of script population) show a related symptom: item 0 receives the pointer position untransformed, so its hit area behaves as if it covered the whole canvas and co-fires alongside every other tile.
- Assigning an empty artboard over a populated slot (teardown) also breaks hit-testing of a sibling slot's live content until that sibling is re-mounted with the two-step pattern.
Happy to provide a minimal repro project + .riv on request.
Environment
rive: 0.14.9,rive_native: 0.1.9(wasm artifact@rive-app/flutter-native-wasm@42.0.0)Setup
LayoutComponentwith an ArtboardComponentList oftileartboards. Each tile's state machine has native pointer listeners (Pointer Down / Pointer Enter) that fire view-model triggers.tileNumber).Actual behavior
If the slot assignment and the list population run in the same synchronous batch (e.g. inside a setup call chain executed from a timer/Future), the mounted content renders perfectly and accepts VM writes (colors, etc.), but its tile pointer listeners never fire — permanently. Hit-testing silently skips the content forever, no matter how much time passes or how many pointer events arrive later. Listeners belonging to the root artboard's own state machine keep working the whole time.
Expected behavior
Programmatic/deferred structural mutations (artboard-property rebinds + component-list population) should converge for hit-testing the same way they do for rendering.
What does NOT fix it (all tried)
stateMachine.advanceAndApply(0)after every structural boundary (slot assign,tileNumberset, per-item color writes) — even though it internally runs up to 5 update passes.RiveWidgetController.pointerEvent(hover/down/up at a neutral position) followed by zero-dt advances.Workaround that DOES fix it
Splitting the mutation into two separately scheduled steps outside the setup batch:
tileNumber(script populates the list) and write the colors;with
advanceAndApply(0)after each boundary. With that separation the same native listeners fire reliably — mouse, drag and touch, across many rounds, including re-populating a live canvas (changingtileNumberon an already-mounted canvas).Additional data points
Listener.onPointerDown) always converge — consistent with the special-casedstateMachine.advanceAndApply(0)for down/up/cancel inRiveWidgetPainter.pointerEvent(rive 0.14.9widget_controller.dart) — while identical call bodies executed from timers never converge, even with the same explicit advance calls.StateMachineInstancehit-testing skips component-list items whoseworldToLocalis unavailable (state_machine_instance.cpp~line 1060continue), and an artboard-property assignment only marks binding dirt (viewmodel_instance_artboard.cpp:24) with the actual mount deferred toupdatePass.ArtboardComponentListfrom Dart (list.add(...)instead of script population) show a related symptom: item 0 receives the pointer position untransformed, so its hit area behaves as if it covered the whole canvas and co-fires alongside every other tile.Happy to provide a minimal repro project + .riv on request.