Skip to content

[Android] Calling inPath:measure() inside a ScriptedPathEffect kills the effect — silently #440

Description

@lsadsad

[Android] Calling inPath:measure() inside a ScriptedPathEffect kills the effect — silently

READY TO FILE. Prepared 2026-08-24 from issue dnar; minimal repro
verified on both platforms. This file is the source of record for the report
body — if maintainers ask for edits, change it here and update the issue to
match. May be re-routed to rive-app/rive-android, same as #439.

Reproduction repository: https://github.com/lsadsad/rive-android-scripted-patheffect-repro
(clone → npm installnpx expo run:android)

Summary

A Luau ScriptedPathEffect that calls inPath:measure() inside its update
stops executing entirely on Android. Not "runs and returns an empty path" —
the effect body never runs at all. The same effect, same file, same host code
works on iOS.

The trigger is narrow, and isolated to a single call. An effect that
generates geometry from scratch works fine on Android. Add one line that
measures the incoming path, and the effect dies.

Completely silent: no error, no warning, no onError, and nothing in
adb logcat beyond routine Nitro.HybridObjectPrototype lines.

The isolating experiment

One .riv, one artboard, one shape, one effect. Two versions of the same
script, each run on both platforms.

Version A — generate from scratch (ignores inPath; builds a ring segment
with moveTo/lineTo):

iOS Android
renders yes yes
scriptRan 2 1

Version B — derive from inPath (identical except it calls
inPath:measure() and measure:extract(...)):

iOS Android
renders yes no
scriptRan 3 0
inPathLen 869.117 0

Same artboard, same paints, same host code, same runtime pins. The only
difference between A and B is that B touches inPath.

What the sentinels tell you

The effect writes back two view-model numbers, surfaced live in the app:

  • scriptRan — incremented at the very top of update, before any use of inPath.
  • inPathLen — the measured length of the incoming path.

On Android, version B reports scriptRan = 0. Since that increment happens
before measure() is ever reached, the effect body is not completing at all —
this is not a case of the body running and finding an empty path. inPathLen = 0
is consistent with that.

On iOS the same build reports scriptRan = 3, inPathLen = 869.117, and
renders the extracted sub-path correctly.

The script

function update(self: ArcEffect, inPath: PathData, node: NodeReadData): PathData
	local out = Path.new()

	self.count = self.count + 1          -- sentinel, BEFORE any inPath use
	if self.ran then self.ran.value = self.count end

	local measure = inPath:measure()     -- <-- adding this line breaks Android
	local total = measure.length
	if self.len then self.len.value = total end
	if total <= 0.0 then return out end

	local percent = 0.0
	if self.sweep then percent = self.sweep.value end
	if percent <= 0.0 then return out end

	measure:extract(0.0, total * (percent / 100.0), out, true)
	return out
end

Reproduction

npm install
npx expo run:ios       # renders; scriptRan climbs, inPathLen ~ 869
npx expo run:android   # nothing renders; scriptRan = 0, inPathLen = 0

The probe artboard is a single shape with a placeholder triangle path, a fill
and a stroke, and the ScriptedPathEffect on both. No nested artboards, no
artboard lists, no property groups.

Environment

@rive-app/react-native 0.4.19
react-native-nitro-modules 0.35.10
rive-android 11.7.2 (pinned by the package's runtimeVersions.android)
RiveRuntime (iOS) 6.21.1
React Native 0.85.3 (Expo SDK 56.0.18, new architecture)
Android emulator, API 36 (Android 16), arm64 — FAILS
iOS simulator, iPhone 17 Pro — works

How we found it

This began as a production component whose donut arcs render on iOS and on web
(@rive-app/canvas) but not on Android, from an md5-identical file. That
component's effect derives its slice geometry from the incoming path via
PathMeasure/extract — the same pattern as version B.

Ruled out along the way, so you don't have to: ScriptedPathEffect being
unsupported on Android (a sibling component uses one and renders fine), Luau not
running on Android (same counter-example), binding the wrong view-model
instance, intro/timing artifacts, emulator staleness, nested-artboard
containment (the failing component has none), and stroke-vs-fill (the probe puts
the same effect on a fill and a stroke; both behave identically).

Possibly related

#439 — nested-artboard
Property Groups never advance on Android. Same runtime, same signature: renders
convincingly, silently omits the thing under test. We don't know whether they
share a root cause.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions