Skip to content

feat(hooks): async instance creation via useViewModelInstance({ async: true })#331

Open
mfazekas wants to merge 3 commits into
mainfrom
feat/use-viewmodel-instance-async-legacy
Open

feat(hooks): async instance creation via useViewModelInstance({ async: true })#331
mfazekas wants to merge 3 commits into
mainfrom
feat/use-viewmodel-instance-async-legacy

Conversation

@mfazekas

@mfazekas mfazekas commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Port of #304 (merged to feat/rive-ios-experimental) to main.

useViewModelInstance(source, { async: true }) creates the instance via the *Async runtime APIs off the JS thread and returns { instance, isLoading, error }. The sync overloads are deprecated per-overload (they block the JS thread via deprecated runtime APIs); async: true becomes the default in the next major. Overload/deprecation resolution is pinned with tsd (yarn typetest, gated in the CI lint job).

Native side: the *Async lookups now hop to the main thread (Android riveMainScope, iOS Promise.onMain) since the legacy runtime has no internal synchronization (the #297 race class), and Android's getViewModelInstance() returns a main-thread-maintained snapshot instead of traversing the controller off-thread, without blocking JS.

Porting notes:

  1. Experimental's legacy/ sources map onto this branch's flat layout (android/src/main, ios/); the new/-backend and expo57 harness changes from feat(hooks): async instance creation via useViewModelInstance({ async: true }) #304 don't apply here.
  2. src/hooks/useViewModelInstance.ts and the ported example files are taken from the experimental branch (re-run through main's prettier), which also brings a small pre-feat(hooks): async instance creation via useViewModelInstance({ async: true }) #304 improvement: createInstanceByName failures on the sync path now console.warn and resolve null instead of throwing raw native errors.
  3. The new e2e harness drops the android-experimental skips (RiveFileFactory.getBackend() doesn't exist here) and adds the arbtboards-models-instances.riv fixture.

Behavioral notes (worth a release-note callout):

  1. Android riveViewRef.getViewModelInstance() is now eventually consistent off the main thread: it returns the last main-thread snapshot and schedules a refresh, instead of the previous unsynchronized live read (the iOS Crash: Occasional crashes #297 race). One-shot readers — including the deprecated sync useViewModelInstance(riveViewRef) — can observe null where the racy read happened to catch a late auto-bind; the async: true path polls and is unaffected.
  2. useRive().riveViewRef now starts as undefined (view pending) instead of null (failed/detached), mirroring the useRiveFile convention, and its type widens to RiveViewRef | null | undefined.
  3. On the sync hook, a null source now settles to a terminal { instance: null, isLoading: false } instead of reporting undefined/loading forever.

mfazekas added 3 commits July 10, 2026 13:29
…: true })

Port of #304 from feat/rive-ios-experimental to main. The instance is
created via the *Async runtime APIs off the JS thread; the hook returns
{ instance, isLoading, error } and the sync overloads are deprecated
per-overload. Natives main-hop the *Async lookups (Android riveMainScope,
iOS Promise.onMain) and Android's getViewModelInstance() returns a
main-thread-maintained snapshot instead of traversing the controller
off-thread (issue #297 race class).
The ported comments compared "the new backend" to "the legacy backend",
which only reads on feat/rive-ios-experimental where both exist. Reword
so a reader of this branch knows the divergent behavior lives on the
experimental backend and why the hook still handles it here.
lastKnownViewModelInstance survived dispose, so a JS caller still holding
the view ref got the retained, already-released instance back (first
property access throws "Cannot acquire a disposed object") instead of
null. Clear it on the disposing detach, and resolve null from the
main-thread read once willDispose is set so a refresh posted before
dispose can't re-cache a released instance afterwards.
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