Skip to content

WIP feat: experimental iOS / Android POC#134

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

WIP feat: experimental iOS / Android POC#134
mfazekas wants to merge 57 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}")

@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}")

@mfazekas mfazekas force-pushed the feat/rive-ios-experimental branch from 8134a07 to ec12673 Compare February 17, 2026 12:11
@mfazekas mfazekas changed the title feat: experimental iOS API support WIP feat: experimental iOS / Android POC Feb 17, 2026
@mfazekas mfazekas force-pushed the feat/rive-ios-experimental branch 2 times, most recently from 91e2fb6 to cfb2ff6 Compare February 19, 2026 13:50
@mfazekas mfazekas force-pushed the feat/rive-ios-experimental branch from cfb2ff6 to 44681b5 Compare February 26, 2026 13:57
@mfazekas mfazekas force-pushed the feat/rive-ios-experimental branch from 44681b5 to 91c487b Compare March 11, 2026 10:46
mfazekas and others added 14 commits March 16, 2026 10:37
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.
mfazekas and others added 5 commits March 16, 2026 10:37
- Always use SPM for RiveRuntime (remove CocoaPods fallback)
- Add RiveSPMEmbedFix module to auto-embed RiveRuntime.framework
- Pin SPM to exactVersion instead of upToNextMajorVersion
- Replace USE_RIVE_SPM with USE_RIVE_EXPERIMENTAL_RUNTIME to select
  legacy vs experimental backend independently of SPM

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ener

All backends now emit the current value as the first listener emission,
so hooks no longer need a synchronous property.value read on mount.

- useRiveProperty: always starts undefined; value arrives via the
  listener's first callback (not a sync read). Setter updater fn now
  uses tracked React state instead of property.value.
- iOS legacy: Number/String/Boolean/Enum addListener now emits the
  current value synchronously on subscribe, matching the stream-based
  behaviour of the experimental backend (valueStream) and Android
  (Flow with drop=0). No native changes needed elsewhere.
- Tests: mock addListener emits current value on subscribe; updated
  test description to reflect the new async-first semantics.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…erimental iOS test issues

- Rename USE_RIVE_EXPERIMENTAL_RUNTIME to USE_RIVE_NEW_API (matches Android)
- Remove USE_RIVE_SPM and all SPM embedding hacks from podspec/Podfile
- Use standard CocoaPods dependency for RiveRuntime
- Emit initial value in experimental addListener (number/string/bool/enum/color)
- Guard tests that crash on experimental iOS (list ops, autoPlay, artboard/image loading)
- Handle createInstanceByName throwing on experimental backend
@mfazekas mfazekas force-pushed the feat/rive-ios-experimental branch from f1b851e to aa2fdf0 Compare March 16, 2026 09:39
mfazekas and others added 24 commits March 16, 2026 10:53
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…leFactory

ArrayBufferHolder is a C++ type; the Swift protocol uses ArrayBuffer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deprecate the sync properties in favour of non-blocking async methods:
- `viewModelCount` → `getViewModelCountAsync()`
- `artboardCount`  → `getArtboardCountAsync()`
- `artboardNames`  → `getArtboardNamesAsync()`

Implemented across all four backends (iOS new/legacy, Android experimental/legacy)
and regenerated nitrogen bindings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add getValueAsync(), getLengthAsync(), and getInstanceAtAsync() to all
ViewModel property types (Number, String, Boolean, Color, Enum, List)
across all four backends (iOS new/legacy, Android experimental/legacy).
Sync versions are kept but deprecated in favour of the new async methods.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s, extract shared async helpers

- Add missing `import com.margelo.nitro.core.Promise` to all 6 experimental
  Android ViewModel property files (Boolean, Color, Enum, List, Number, String)
- Extract private async helpers in iOS new Color and List to eliminate
  non-trivial code duplication between deprecated sync and async methods

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Android Kotlin Style Guide (android: true) is not used by rive-android
itself and introduces rules like chain-method-continuation that don't apply
well to React Native bridge/Nitro bindings code.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Auto-fixed code style violations via ktlint --format
- Disabled if-else-wrapping and condition-wrapping rules in .editorconfig
  (non-bug style opinions inconsistent with project's permissive approach)
- Added scripts/lint-fix-kotlin.sh and yarn lint:fix:kotlin for auto-fixing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ading

Adds an optional `type` field ('image' | 'font' | 'audio') to
ResolvedReferencedAsset so callers can declare asset types explicitly,
avoiding the deprecated extension/magic-byte inference fallback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pe field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nctions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the old string-based `AssetType(fromExplicit:)` initializer and call site
with the new `AssetType(from: RiveAssetType)` initializer that takes the typed enum
directly from the nitrogen-generated `asset.type` property.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pe enum

Replace string-based explicit type comparison with a proper enum `when` expression
now that nitrogen generates `asset.type` as `RiveAssetType?` instead of `String?`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ame, set() method

- Add `validate?: boolean` (default true) to `viewModelByNameAsync` on all 4 backends;
  experimental backends skip the getViewModelNames() round-trip when false, legacy
  backends ignore it (native call already returns nil/null for unknown names)
- Rename `createInstanceAsync()` → `createBlankInstanceAsync()` across spec and all
  4 backends to make the semantics (blank/empty instance) explicit
- Add `set(value)` fire-and-forget method to all 5 property types (Number, String,
  Boolean, Color, Enum) as the recommended write path; fully deprecate `value` getter
  and setter
- Deprecate `viewModelCount`, `viewModelByIndex`, `viewModelByName` in favour of
  `getViewModelNamesAsync()` + `viewModelByNameAsync()`; remove removed
  `getViewModelCountAsync` / `viewModelByIndexAsync` variants
- Regenerate nitrogen/ outputs and apply ktlint auto-format

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Nitrogen generates `func set(value: T)` with an external `value:` label,
but our implementations had `func set(_ value: T)` with no external label.
Fixes Swift build error: "instance method 'set' has different argument labels
from those required by protocol 'Hybrid*PropertySpec_protocol' ('set(value:)')".
Also updates the deprecated `value` setter delegate to `try? set(value: newValue)`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds an explicit test for the contract that `useRiveNumber` delivers its
initial value purely from the first listener emission on subscribe — no
prop change is required. Uses a tight 1 s timeout to make it fail fast
if the hook ever accidentally regresses to relying on a prop update cycle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mirror of the existing Data Binding exerciser but with WithViewModelSetup
replaced to use `defaultArtboardViewModelAsync()` and
`createDefaultInstanceAsync()` instead of the deprecated sync counterparts.
Useful for verifying the async path produces identical visual results.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously both the sync and async variants ignored `index` and always
called createDefaultInstance(). Fix implements createInstanceByIndexImpl
which calls getInstanceNames(of:) on the File and picks the name at the
requested position, then creates via .name(name, from:).

Also adds a post_install Podfile hook that strips the RiveRuntime.Swift
submodule from RiveRuntime's modulemaps. Without it the project cannot be
built with Xcode 26 / Swift 6.2 because Clang sees conflicting definitions
of swift::Optional / swift::String between the pre-built RiveRuntime
XCFramework (Swift 6.1 ABI) and the freshly-compiled NitroModules
(Swift 6.2 ABI).

Closes issue 1 (iOS) from .local/docs/issues.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Android

Remove the async variant — callers should use createInstanceByNameAsync
instead. The deprecated sync createInstanceByIndex now correctly uses
getViewModelInstanceNames + index lookup on Android (was returning
default instance). Tests updated to use the sync API.
Were hardcoded to 0. Now use file.getProperties(of:) and
file.getInstanceNames(of:) respectively. Tests strengthened to
assert > 0 instead of >= 0.
Was accepting both replaced and original values. Now strictly asserts
the replacement took effect. Passes on iOS; Android experimental SDK
lacks the native bridge method (known limitation).
viewModelImpl was passing the property path as instanceName, which is
misleading. The SDK doesn't expose the actual instance name, so leave
it as "" (consistent with Android and Issue 11 limitation).
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