Skip to content

WIP feat: experimental iOS / Android POC#134

Draft
mfazekas wants to merge 21 commits intomainfrom
feat/rive-ios-experimental
Draft

WIP feat: experimental iOS / Android POC#134
mfazekas wants to merge 21 commits intomainfrom
feat/rive-ios-experimental

Conversation

@mfazekas
Copy link
Collaborator

@mfazekas mfazekas commented Jan 23, 2026

Summary

  • Converts entire Rive API from async (Promise-based) to synchronous
  • Adds optional SPM integration for RiveRuntime 6.15.0 (experimental API)
  • Experimental iOS backend uses blockingAsync helper to bridge async rive-ios API to sync interface
  • Legacy iOS and Android backends simply remove Promise.async {} wrappers (underlying SDK is already sync)
  • Simplifies React hooks (useViewModelInstance, useRiveProperty) — no more useState + useEffect for async loading
  • Properties use value getter/setter instead of getValue()/setValue()
  • Android experimental backend: Custom TextureView renderer with Choreographer render loop, CommandQueue polling, ViewModel data binding via reflection, and property validation with error logging

Usage

# iOS experimental
USE_RIVE_SPM=1 pod install

# Android experimental
# Set in example/android/gradle.properties:
USE_RIVE_NEW_API=true

Without the flags, the legacy backends are used.

TODO

  • Migrate TS API to async methods (CommandQueue operations are inherently async)
  • Review logger/Android error handling
  • SPM, right now we're using SPM but that only links with Rive if frameworks are not enabled, the dynamic lib is not embedded into the app. This works with debugger but not with prod build
  • OOB asset type inference — Experimental backend pre-registers assets globally before file creation, so unlike legacy it doesn't get type info from RiveFileAsset subclasses. Currently inferred from file extensions and magic bytes, but fails for assets with no extension (e.g. referenced_audio-2929340). Fix: add optional type in field to ResolvedReferencedAsset. This fix is in RN.
  • OOB live asset replacementupdateReferencedAssets doesn't work in experimental backend. Legacy caches RiveFileAsset objects and mutates them in-place (imageAsset.renderImage()). Experimental only has worker.addGlobalImageAsset() consumed at file load time. ViewModel image properties (imgProp.set()) work fine via data binding — only affects OOB file-level assets.
  • Color properties not public — Color property getters/setters are internal in experimental rive-ios API. Upstream: rive-ios#414
  • CADisplayLink use-after-free on teardown — After interacting with a view then navigating back, rive::CommandQueue::processMessages() crashes with EXC_BAD_ACCESS. The CADisplayLink fires after the Worker/ViewModelInstance are deallocated. Upstream: rive-ios#418, CDN variant: rive-ios#410
  • valueStream throws missingData on empty string — Setting a nested ViewModel string property to "" kills the listener permanently. We work around this by restarting the stream on error. Upstream: rive-ios#416
  • play()/pause()/reset() are no-ops — They set isPaused but nothing reads it. No explicit play/pause, in experimental RiveView.
  • Android pointer events bug (SDK v11.1.0) — .riv files with Rive Listeners fail in experimental CommandQueue API. State machine handle returned but C++ says "State machine not found for advance". Upstream SDK bug, not our wrapper.

@mfazekas mfazekas force-pushed the feat/rive-ios-experimental branch 2 times, most recently from 95816cf to 4fe9e12 Compare January 23, 2026 11:23
@mfazekas mfazekas force-pushed the feat/rive-ios-experimental branch 4 times, most recently from 8485f9f to 9b4acd8 Compare February 9, 2026 10:25
@mfazekas mfazekas changed the title feat: experimental iOS API support (getEnums via SPM) feat: experimental iOS API support Feb 9, 2026
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remaining comments which cannot be posted as a review comment to avoid GitHub Rate Limit

ktlint

🚫 [ktlint] standard:multiline-if-else reported by reviewdog 🐶
Missing { ... }

data[2] == 0x54.toByte() && data[3] == 0x4F.toByte()) return AssetType.FONT


🚫 [ktlint] standard:chain-method-continuation reported by reviewdog 🐶
Expected newline before '.'


🚫 [ktlint] standard:chain-method-continuation reported by reviewdog 🐶
Expected newline before '.'


🚫 [ktlint] standard:try-catch-finally-spacing reported by reviewdog 🐶
Expected a newline after '{'


🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline after '{'


🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline before '}'


🚫 [ktlint] standard:try-catch-finally-spacing reported by reviewdog 🐶
Expected a newline before '}'


🚫 [ktlint] standard:try-catch-finally-spacing reported by reviewdog 🐶
Expected a newline after '{'


🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline after '{'


🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline before '}'


🚫 [ktlint] standard:try-catch-finally-spacing reported by reviewdog 🐶
Expected a newline before '}'


🚫 [ktlint] standard:no-unused-imports reported by reviewdog 🐶
Unused import


🚫 [ktlint] standard:string-template reported by reviewdog 🐶
Redundant curly braces

Log.d(TAG, "onSurfaceTextureAvailable: ${w}x${h} worker=${this@RiveReactNativeView.riveWorker != null}")


🚫 [ktlint] standard:if-else-wrapping reported by reviewdog 🐶
Expected a newline

val deltaTime = if (lastFrameTimeNs == 0L) Duration.ZERO


🚫 [ktlint] standard:multiline-if-else reported by reviewdog 🐶
Missing { ... }

val deltaTime = if (lastFrameTimeNs == 0L) Duration.ZERO


🚫 [ktlint] standard:if-else-wrapping reported by reviewdog 🐶
Expected a newline

else (frameTimeNanos - lastFrameTimeNs).nanoseconds


🚫 [ktlint] standard:multiline-if-else reported by reviewdog 🐶
Missing { ... }

else (frameTimeNanos - lastFrameTimeNs).nanoseconds


🚫 [ktlint] standard:chain-method-continuation reported by reviewdog 🐶
Expected newline before '.'

Log.d(TAG, "configure: reload=$reload initialUpdate=$initialUpdate fit=$activeFit surfaceTexture=${surfaceTexture != null} surfaceW=${surfaceWidth} surfaceH=${surfaceHeight}")


🚫 [ktlint] standard:argument-list-wrapping reported by reviewdog 🐶
Argument should be on a separate line (unless all arguments can fit a single line)

Log.d(TAG, "configure: reload=$reload initialUpdate=$initialUpdate fit=$activeFit surfaceTexture=${surfaceTexture != null} surfaceW=${surfaceWidth} surfaceH=${surfaceHeight}")


🚫 [ktlint] standard:argument-list-wrapping reported by reviewdog 🐶
Argument should be on a separate line (unless all arguments can fit a single line)

Log.d(TAG, "configure: reload=$reload initialUpdate=$initialUpdate fit=$activeFit surfaceTexture=${surfaceTexture != null} surfaceW=${surfaceWidth} surfaceH=${surfaceHeight}")


🚫 [ktlint] standard:string-template reported by reviewdog 🐶
Redundant curly braces

Log.d(TAG, "configure: reload=$reload initialUpdate=$initialUpdate fit=$activeFit surfaceTexture=${surfaceTexture != null} surfaceW=${surfaceWidth} surfaceH=${surfaceHeight}")


🚫 [ktlint] standard:max-line-length reported by reviewdog 🐶
Exceeded max line length (140)

Log.d(TAG, "configure: reload=$reload initialUpdate=$initialUpdate fit=$activeFit surfaceTexture=${surfaceTexture != null} surfaceW=${surfaceWidth} surfaceH=${surfaceHeight}")


🚫 [ktlint] standard:string-template reported by reviewdog 🐶
Redundant curly braces

Log.d(TAG, "configure: reload=$reload initialUpdate=$initialUpdate fit=$activeFit surfaceTexture=${surfaceTexture != null} surfaceW=${surfaceWidth} surfaceH=${surfaceHeight}")


🚫 [ktlint] standard:argument-list-wrapping reported by reviewdog 🐶
Missing newline before ")"

Log.d(TAG, "configure: reload=$reload initialUpdate=$initialUpdate fit=$activeFit surfaceTexture=${surfaceTexture != null} surfaceW=${surfaceWidth} surfaceH=${surfaceHeight}")


🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline after '{'

val worker = riveWorker ?: run { Log.w(TAG, "touch: no worker"); return }


🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline after ';'

val worker = riveWorker ?: run { Log.w(TAG, "touch: no worker"); return }


🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline before '}'

val worker = riveWorker ?: run { Log.w(TAG, "touch: no worker"); return }


🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline after '{'

val smHandle = stateMachineHandle ?: run { Log.w(TAG, "touch: no smHandle"); return }


🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline after ';'

val smHandle = stateMachineHandle ?: run { Log.w(TAG, "touch: no smHandle"); return }


🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline before '}'

val smHandle = stateMachineHandle ?: run { Log.w(TAG, "touch: no smHandle"); return }


🚫 [ktlint] standard:if-else-wrapping reported by reviewdog 🐶
A single line if-statement should be kept simple. The 'THEN' may not be wrapped in a block.

if (w <= 0 || h <= 0) { Log.w(TAG, "touch: invalid surface ${w}x${h}"); return }


🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline after '{'

if (w <= 0 || h <= 0) { Log.w(TAG, "touch: invalid surface ${w}x${h}"); return }


🚫 [ktlint] standard:string-template reported by reviewdog 🐶
Redundant curly braces

if (w <= 0 || h <= 0) { Log.w(TAG, "touch: invalid surface ${w}x${h}"); return }


🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline after ';'

if (w <= 0 || h <= 0) { Log.w(TAG, "touch: invalid surface ${w}x${h}"); return }


🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline before '}'

if (w <= 0 || h <= 0) { Log.w(TAG, "touch: invalid surface ${w}x${h}"); return }


🚫 [ktlint] standard:chain-method-continuation reported by reviewdog 🐶
Expected newline before '.'

val legacyFile = app.rive.runtime.kotlin.core.File(bytes)


🚫 [ktlint] standard:chain-method-continuation reported by reviewdog 🐶
Expected newline before '.'

Log.d("ComposeRiveTest", "[$label] input[$j]: name=${input.name} isBoolean=${input.isBoolean} isTrigger=${input.isTrigger} isNumber=${input.isNumber}")


🚫 [ktlint] standard:argument-list-wrapping reported by reviewdog 🐶
Argument should be on a separate line (unless all arguments can fit a single line)

Log.d("ComposeRiveTest", "[$label] input[$j]: name=${input.name} isBoolean=${input.isBoolean} isTrigger=${input.isTrigger} isNumber=${input.isNumber}")


🚫 [ktlint] standard:argument-list-wrapping reported by reviewdog 🐶
Argument should be on a separate line (unless all arguments can fit a single line)

Log.d("ComposeRiveTest", "[$label] input[$j]: name=${input.name} isBoolean=${input.isBoolean} isTrigger=${input.isTrigger} isNumber=${input.isNumber}")


🚫 [ktlint] standard:max-line-length reported by reviewdog 🐶
Exceeded max line length (140)

Log.d("ComposeRiveTest", "[$label] input[$j]: name=${input.name} isBoolean=${input.isBoolean} isTrigger=${input.isTrigger} isNumber=${input.isNumber}")


🚫 [ktlint] standard:argument-list-wrapping reported by reviewdog 🐶
Missing newline before ")"

Log.d("ComposeRiveTest", "[$label] input[$j]: name=${input.name} isBoolean=${input.isBoolean} isTrigger=${input.isTrigger} isNumber=${input.isNumber}")


🚫 [ktlint] standard:function-naming reported by reviewdog 🐶
Function name should start with a lowercase letter (except factory methods) and use camel case


🚫 [ktlint] standard:chain-method-continuation reported by reviewdog 🐶
Expected newline before '.'

Log.d("ComposeRiveTest", "artboard=${artboard.artboardHandle} sm=${stateMachine.stateMachineHandle} name=${artboard.name} smName=${stateMachine.name}")


🚫 [ktlint] standard:argument-list-wrapping reported by reviewdog 🐶
Argument should be on a separate line (unless all arguments can fit a single line)

Log.d("ComposeRiveTest", "artboard=${artboard.artboardHandle} sm=${stateMachine.stateMachineHandle} name=${artboard.name} smName=${stateMachine.name}")


🚫 [ktlint] standard:argument-list-wrapping reported by reviewdog 🐶
Argument should be on a separate line (unless all arguments can fit a single line)

Log.d("ComposeRiveTest", "artboard=${artboard.artboardHandle} sm=${stateMachine.stateMachineHandle} name=${artboard.name} smName=${stateMachine.name}")


🚫 [ktlint] standard:max-line-length reported by reviewdog 🐶
Exceeded max line length (140)

Log.d("ComposeRiveTest", "artboard=${artboard.artboardHandle} sm=${stateMachine.stateMachineHandle} name=${artboard.name} smName=${stateMachine.name}")


🚫 [ktlint] standard:argument-list-wrapping reported by reviewdog 🐶
Missing newline before ")"

Log.d("ComposeRiveTest", "artboard=${artboard.artboardHandle} sm=${stateMachine.stateMachineHandle} name=${artboard.name} smName=${stateMachine.name}")

if (data.size < 4) return AssetType.IMAGE

// PNG: 89 50 4E 47
if (data[0] == 0x89.toByte() && data[1] == 0x50.toByte() &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:condition-wrapping reported by reviewdog 🐶
Newline expected before operand in multiline condition


// PNG: 89 50 4E 47
if (data[0] == 0x89.toByte() && data[1] == 0x50.toByte() &&
data[2] == 0x4E.toByte() && data[3] == 0x47.toByte()) return AssetType.IMAGE
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:condition-wrapping reported by reviewdog 🐶
Newline expected before operand in multiline condition


// PNG: 89 50 4E 47
if (data[0] == 0x89.toByte() && data[1] == 0x50.toByte() &&
data[2] == 0x4E.toByte() && data[3] == 0x47.toByte()) return AssetType.IMAGE
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:wrapping reported by reviewdog 🐶
Missing newline before ")"


// PNG: 89 50 4E 47
if (data[0] == 0x89.toByte() && data[1] == 0x50.toByte() &&
data[2] == 0x4E.toByte() && data[3] == 0x47.toByte()) return AssetType.IMAGE
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:if-else-wrapping reported by reviewdog 🐶
Expected a newline


// PNG: 89 50 4E 47
if (data[0] == 0x89.toByte() && data[1] == 0x50.toByte() &&
data[2] == 0x4E.toByte() && data[3] == 0x47.toByte()) return AssetType.IMAGE
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:multiline-if-else reported by reviewdog 🐶
Missing { ... }

data[2] == 0x33.toByte()) return AssetType.AUDIO
// TrueType: 00 01 00 00
if (data[0] == 0x00.toByte() && data[1] == 0x01.toByte() &&
data[2] == 0x00.toByte() && data[3] == 0x00.toByte()) return AssetType.FONT
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:multiline-if-else reported by reviewdog 🐶
Missing { ... }

if (data[0] == 0x00.toByte() && data[1] == 0x01.toByte() &&
data[2] == 0x00.toByte() && data[3] == 0x00.toByte()) return AssetType.FONT
// OpenType: 4F 54 54 4F ("OTTO")
if (data[0] == 0x4F.toByte() && data[1] == 0x54.toByte() &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:condition-wrapping reported by reviewdog 🐶
Newline expected before operand in multiline condition

data[2] == 0x00.toByte() && data[3] == 0x00.toByte()) return AssetType.FONT
// OpenType: 4F 54 54 4F ("OTTO")
if (data[0] == 0x4F.toByte() && data[1] == 0x54.toByte() &&
data[2] == 0x54.toByte() && data[3] == 0x4F.toByte()) return AssetType.FONT
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:condition-wrapping reported by reviewdog 🐶
Newline expected before operand in multiline condition

data[2] == 0x00.toByte() && data[3] == 0x00.toByte()) return AssetType.FONT
// OpenType: 4F 54 54 4F ("OTTO")
if (data[0] == 0x4F.toByte() && data[1] == 0x54.toByte() &&
data[2] == 0x54.toByte() && data[3] == 0x4F.toByte()) return AssetType.FONT
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:wrapping reported by reviewdog 🐶
Missing newline before ")"

data[2] == 0x00.toByte() && data[3] == 0x00.toByte()) return AssetType.FONT
// OpenType: 4F 54 54 4F ("OTTO")
if (data[0] == 0x4F.toByte() && data[1] == 0x54.toByte() &&
data[2] == 0x54.toByte() && data[3] == 0x4F.toByte()) return AssetType.FONT
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:if-else-wrapping reported by reviewdog 🐶
Expected a newline

@mfazekas mfazekas force-pushed the feat/rive-ios-experimental branch from dd06b3a to a5855c9 Compare February 16, 2026 10:41
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remaining comments which cannot be posted as a review comment to avoid GitHub Rate Limit

ktlint

🚫 [ktlint] standard:if-else-wrapping reported by reviewdog 🐶
A single line if-statement should be kept simple. The 'THEN' may not be wrapped in a block.

if (w <= 0 || h <= 0) { Log.w(TAG, "touch: invalid surface ${w}x${h}"); return }


🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline after '{'

if (w <= 0 || h <= 0) { Log.w(TAG, "touch: invalid surface ${w}x${h}"); return }


🚫 [ktlint] standard:string-template reported by reviewdog 🐶
Redundant curly braces

if (w <= 0 || h <= 0) { Log.w(TAG, "touch: invalid surface ${w}x${h}"); return }


🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline after ';'

if (w <= 0 || h <= 0) { Log.w(TAG, "touch: invalid surface ${w}x${h}"); return }


🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline before '}'

if (w <= 0 || h <= 0) { Log.w(TAG, "touch: invalid surface ${w}x${h}"); return }


🚫 [ktlint] standard:chain-method-continuation reported by reviewdog 🐶
Expected newline before '.'

val legacyFile = app.rive.runtime.kotlin.core.File(bytes)


🚫 [ktlint] standard:chain-method-continuation reported by reviewdog 🐶
Expected newline before '.'

Log.d("ComposeRiveTest", "[$label] input[$j]: name=${input.name} isBoolean=${input.isBoolean} isTrigger=${input.isTrigger} isNumber=${input.isNumber}")


🚫 [ktlint] standard:argument-list-wrapping reported by reviewdog 🐶
Argument should be on a separate line (unless all arguments can fit a single line)

Log.d("ComposeRiveTest", "[$label] input[$j]: name=${input.name} isBoolean=${input.isBoolean} isTrigger=${input.isTrigger} isNumber=${input.isNumber}")


🚫 [ktlint] standard:argument-list-wrapping reported by reviewdog 🐶
Argument should be on a separate line (unless all arguments can fit a single line)

Log.d("ComposeRiveTest", "[$label] input[$j]: name=${input.name} isBoolean=${input.isBoolean} isTrigger=${input.isTrigger} isNumber=${input.isNumber}")


🚫 [ktlint] standard:max-line-length reported by reviewdog 🐶
Exceeded max line length (140)

Log.d("ComposeRiveTest", "[$label] input[$j]: name=${input.name} isBoolean=${input.isBoolean} isTrigger=${input.isTrigger} isNumber=${input.isNumber}")


🚫 [ktlint] standard:argument-list-wrapping reported by reviewdog 🐶
Missing newline before ")"

Log.d("ComposeRiveTest", "[$label] input[$j]: name=${input.name} isBoolean=${input.isBoolean} isTrigger=${input.isTrigger} isNumber=${input.isNumber}")


🚫 [ktlint] standard:function-naming reported by reviewdog 🐶
Function name should start with a lowercase letter (except factory methods) and use camel case


🚫 [ktlint] standard:chain-method-continuation reported by reviewdog 🐶
Expected newline before '.'

Log.d("ComposeRiveTest", "artboard=${artboard.artboardHandle} sm=${stateMachine.stateMachineHandle} name=${artboard.name} smName=${stateMachine.name}")


🚫 [ktlint] standard:argument-list-wrapping reported by reviewdog 🐶
Argument should be on a separate line (unless all arguments can fit a single line)

Log.d("ComposeRiveTest", "artboard=${artboard.artboardHandle} sm=${stateMachine.stateMachineHandle} name=${artboard.name} smName=${stateMachine.name}")


🚫 [ktlint] standard:argument-list-wrapping reported by reviewdog 🐶
Argument should be on a separate line (unless all arguments can fit a single line)

Log.d("ComposeRiveTest", "artboard=${artboard.artboardHandle} sm=${stateMachine.stateMachineHandle} name=${artboard.name} smName=${stateMachine.name}")


🚫 [ktlint] standard:max-line-length reported by reviewdog 🐶
Exceeded max line length (140)

Log.d("ComposeRiveTest", "artboard=${artboard.artboardHandle} sm=${stateMachine.stateMachineHandle} name=${artboard.name} smName=${stateMachine.name}")


🚫 [ktlint] standard:argument-list-wrapping reported by reviewdog 🐶
Missing newline before ")"

Log.d("ComposeRiveTest", "artboard=${artboard.artboardHandle} sm=${stateMachine.stateMachineHandle} name=${artboard.name} smName=${stateMachine.name}")

data[2] == 0x00.toByte() && data[3] == 0x00.toByte()) return AssetType.FONT
// OpenType: 4F 54 54 4F ("OTTO")
if (data[0] == 0x4F.toByte() && data[1] == 0x54.toByte() &&
data[2] == 0x54.toByte() && data[3] == 0x4F.toByte()) return AssetType.FONT
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:multiline-if-else reported by reviewdog 🐶
Missing { ... }

import androidx.annotation.Keep
import app.rive.RiveFile
import app.rive.ViewModelInstance
import app.rive.ViewModelInstanceSource
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:no-unused-imports reported by reviewdog 🐶
Unused import

layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
surfaceTextureListener = object : TextureView.SurfaceTextureListener {
override fun onSurfaceTextureAvailable(st: SurfaceTexture, w: Int, h: Int) {
Log.d(TAG, "onSurfaceTextureAvailable: ${w}x${h} worker=${this@RiveReactNativeView.riveWorker != null}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:string-template reported by reviewdog 🐶
Redundant curly braces

override fun doFrame(frameTimeNanos: Long) {
if (!renderLoopRunning) return

val deltaTime = if (lastFrameTimeNs == 0L) Duration.ZERO
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:if-else-wrapping reported by reviewdog 🐶
Expected a newline

override fun doFrame(frameTimeNanos: Long) {
if (!renderLoopRunning) return

val deltaTime = if (lastFrameTimeNs == 0L) Duration.ZERO
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:multiline-if-else reported by reviewdog 🐶
Missing { ... }

}

private fun handlePointerEvent(event: MotionEvent) {
val worker = riveWorker ?: run { Log.w(TAG, "touch: no worker"); return }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline after ';'

}

private fun handlePointerEvent(event: MotionEvent) {
val worker = riveWorker ?: run { Log.w(TAG, "touch: no worker"); return }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline before '}'


private fun handlePointerEvent(event: MotionEvent) {
val worker = riveWorker ?: run { Log.w(TAG, "touch: no worker"); return }
val smHandle = stateMachineHandle ?: run { Log.w(TAG, "touch: no smHandle"); return }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline after '{'


private fun handlePointerEvent(event: MotionEvent) {
val worker = riveWorker ?: run { Log.w(TAG, "touch: no worker"); return }
val smHandle = stateMachineHandle ?: run { Log.w(TAG, "touch: no smHandle"); return }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline after ';'


private fun handlePointerEvent(event: MotionEvent) {
val worker = riveWorker ?: run { Log.w(TAG, "touch: no worker"); return }
val smHandle = stateMachineHandle ?: run { Log.w(TAG, "touch: no smHandle"); return }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline before '}'

val smHandle = stateMachineHandle ?: run { Log.w(TAG, "touch: no smHandle"); return }
val w = surfaceWidth.toFloat()
val h = surfaceHeight.toFloat()
if (w <= 0 || h <= 0) { Log.w(TAG, "touch: invalid surface ${w}x${h}"); return }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:if-else-wrapping reported by reviewdog 🐶
A single line if-statement should be kept simple. The 'THEN' may not be wrapped in a block.

val smHandle = stateMachineHandle ?: run { Log.w(TAG, "touch: no smHandle"); return }
val w = surfaceWidth.toFloat()
val h = surfaceHeight.toFloat()
if (w <= 0 || h <= 0) { Log.w(TAG, "touch: invalid surface ${w}x${h}"); return }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline after '{'

val smHandle = stateMachineHandle ?: run { Log.w(TAG, "touch: no smHandle"); return }
val w = surfaceWidth.toFloat()
val h = surfaceHeight.toFloat()
if (w <= 0 || h <= 0) { Log.w(TAG, "touch: invalid surface ${w}x${h}"); return }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:string-template reported by reviewdog 🐶
Redundant curly braces

val smHandle = stateMachineHandle ?: run { Log.w(TAG, "touch: no smHandle"); return }
val w = surfaceWidth.toFloat()
val h = surfaceHeight.toFloat()
if (w <= 0 || h <= 0) { Log.w(TAG, "touch: invalid surface ${w}x${h}"); return }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline after ';'

val smHandle = stateMachineHandle ?: run { Log.w(TAG, "touch: no smHandle"); return }
val w = surfaceWidth.toFloat()
val h = surfaceHeight.toFloat()
if (w <= 0 || h <= 0) { Log.w(TAG, "touch: invalid surface ${w}x${h}"); return }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline before '}'

val stateMachine = rememberStateMachine(artboard)

LaunchedEffect(stateMachine) {
Log.d("ComposeRiveTest", "artboard=${artboard.artboardHandle} sm=${stateMachine.stateMachineHandle} name=${artboard.name} smName=${stateMachine.name}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:chain-method-continuation reported by reviewdog 🐶
Expected newline before '.'

val stateMachine = rememberStateMachine(artboard)

LaunchedEffect(stateMachine) {
Log.d("ComposeRiveTest", "artboard=${artboard.artboardHandle} sm=${stateMachine.stateMachineHandle} name=${artboard.name} smName=${stateMachine.name}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:argument-list-wrapping reported by reviewdog 🐶
Argument should be on a separate line (unless all arguments can fit a single line)

val stateMachine = rememberStateMachine(artboard)

LaunchedEffect(stateMachine) {
Log.d("ComposeRiveTest", "artboard=${artboard.artboardHandle} sm=${stateMachine.stateMachineHandle} name=${artboard.name} smName=${stateMachine.name}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:argument-list-wrapping reported by reviewdog 🐶
Argument should be on a separate line (unless all arguments can fit a single line)

val stateMachine = rememberStateMachine(artboard)

LaunchedEffect(stateMachine) {
Log.d("ComposeRiveTest", "artboard=${artboard.artboardHandle} sm=${stateMachine.stateMachineHandle} name=${artboard.name} smName=${stateMachine.name}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:max-line-length reported by reviewdog 🐶
Exceeded max line length (140)

val stateMachine = rememberStateMachine(artboard)

LaunchedEffect(stateMachine) {
Log.d("ComposeRiveTest", "artboard=${artboard.artboardHandle} sm=${stateMachine.stateMachineHandle} name=${artboard.name} smName=${stateMachine.name}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:argument-list-wrapping reported by reviewdog 🐶
Missing newline before ")"

mfazekas and others added 19 commits February 17, 2026 13:03
Add new experimental iOS backend (ios/new/) with synchronous API,
move legacy backend files to ios/legacy/, add getEnums() support,
retry listener streams on missingData, and restore TestComponentOverlay.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
getEnums() in legacy now throws directing users to the experimental
backend instead of creating throwaway Worker+File instances.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
… binding

Each Worker has its own C++ command server with its own m_artboards handle map.
Creating separate Workers per file meant artboard handles from one file were
invalid on another file's server. Using a shared singleton Worker fixes cross-file
artboard property set. Also wires fit/alignment through experimental Fit enum and
improves asset type detection with audio/font magic bytes.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Passing fit to the Rive() constructor breaks layout mode because
the MTKView drawable isn't ready yet. Set rive.fit after
setupRiveUIView() instead.
Port Flutter data binding tests for VM access, enums, creation
variants, list properties, artboard and image properties. Includes
new .riv test assets and react-native-harness upgrade to alpha.25.
…ceByIndex

Prevents fatal crash when passing negative numbers to Swift APIs
expecting unsigned integers.
CocoaPods doesn't embed SPM-resolved dynamic frameworks automatically.
Patches the embed script to include RiveRuntime when USE_RIVE_SPM=1.
Move 19 backend-specific files to src/legacy/java/, add Gradle sourceSets
switching via USE_RIVE_NEW_API property, prepare empty experimental dirs.
Uses the new handle-based Rive Android SDK (app.rive.*) with CommandQueue,
path-based ViewModelInstance property access, and Kotlin Flows for reactivity.
Throws UnsupportedOperationException for SMI inputs, text runs, and events.
Also fixes Gradle property resolution to use rootProject.findProperty.
Custom TextureView renderer with Choreographer render loop, CommandQueue
polling infrastructure, ViewModel resolution and property validation,
and example Compose test activities for manual testing.
Also fix viewModelByIndex/ByName on Android legacy to catch SDK
exceptions instead of letting them propagate as JniExceptions.
…fferences

Fix colorProperty setter Double→Int overflow on Android legacy
(toLong().toInt()). Make enum and replaceViewModel tests tolerant
of Android SDK behavioral differences.
@mfazekas mfazekas force-pushed the feat/rive-ios-experimental branch from 8134a07 to ec12673 Compare February 17, 2026 12:11
val file = riveFile ?: return Promise.resolved(emptyArray())
return Promise.async {
val enums = file.getEnums()
enums.map { enum ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:chain-method-continuation reported by reviewdog 🐶
Expected newline before '.'

try {
// Find a string property to use as identifier for value comparison
val testPropName = vmNames.firstNotNullOfOrNull { name ->
file.getViewModelProperties(name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:chain-method-continuation reported by reviewdog 🐶
Expected newline before '.'


val artboardValue = try {
artboardVmi.getStringFlow(testPropName).first()
} catch (_: Exception) { return vmNames.first() }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:try-catch-finally-spacing reported by reviewdog 🐶
Expected a newline after '{'


val artboardValue = try {
artboardVmi.getStringFlow(testPropName).first()
} catch (_: Exception) { return vmNames.first() }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline after '{'


val artboardValue = try {
artboardVmi.getStringFlow(testPropName).first()
} catch (_: Exception) { return vmNames.first() }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline before '}'


val artboardValue = try {
artboardVmi.getStringFlow(testPropName).first()
} catch (_: Exception) { return vmNames.first() }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:try-catch-finally-spacing reported by reviewdog 🐶
Expected a newline before '}'

try {
val namedValue = try {
namedVmi.getStringFlow(testPropName).first()
} catch (_: Exception) { continue }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:try-catch-finally-spacing reported by reviewdog 🐶
Expected a newline after '{'

try {
val namedValue = try {
namedVmi.getStringFlow(testPropName).first()
} catch (_: Exception) { continue }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline after '{'

try {
val namedValue = try {
namedVmi.getStringFlow(testPropName).first()
} catch (_: Exception) { continue }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:statement-wrapping reported by reviewdog 🐶
Missing newline before '}'

try {
val namedValue = try {
namedVmi.getStringFlow(testPropName).first()
} catch (_: Exception) { continue }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [ktlint] standard:try-catch-finally-spacing reported by reviewdog 🐶
Expected a newline before '}'

@mfazekas mfazekas changed the title feat: experimental iOS API support WIP feat: experimental iOS / Android POC Feb 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant