Skip to content

Conversation

@mfazekas
Copy link
Collaborator

@mfazekas mfazekas commented Dec 11, 2025

Summary

Adds RiveWorkletBridge HybridObject that installs Nitro's dispatcher on Reanimated's UI runtime, enabling ViewModel property listeners to run on the UI thread and update SharedValues without blocking when JS is busy.

  • iOS: Uses GCD dispatch_async/dispatch_sync to main queue
  • Android: Uses Handler(Looper.getMainLooper()) via JNI bridge
  • Exports installWorkletDispatcher() function from package
  • Example app demo with JS thread blocking test
Screen.Recording.2025-12-11.at.10.23.13.mov

Demo Rive file: Bouncing Ball on Rive Community

Usage

1. Install dispatcher once at app startup

import { installWorkletDispatcher } from '@rive-app/react-native';
import { runOnUI } from 'react-native-reanimated';

// Call once at app startup (e.g., in App.tsx or _layout.tsx)
installWorkletDispatcher(runOnUI);

2. Use the UI thread listener pattern

import { useEffect } from 'react';
import { runOnUI, useSharedValue, type SharedValue } from 'react-native-reanimated';
import { NitroModules } from 'react-native-nitro-modules';
import type { ViewModelNumberProperty } from '@rive-app/react-native';

declare global {
  var __callMicrotasks: () => void;
}

function useRiveNumberListener(
  property: ViewModelNumberProperty | undefined,
  sharedValue: SharedValue<number>
) {
  useEffect(() => {
    if (!property) return;

    // 1. Box the property so it can cross JS/UI thread boundary
    const boxedProperty = NitroModules.box(property);
    const sv = sharedValue;

    // 2. Run setup on UI thread
    runOnUI(() => {
      'worklet';
      const prop = boxedProperty.unbox();

      // 3. Add listener with worklet callback
      prop.addListener((value: number) => {
        'worklet';
        sv.value = value;

        // 4. Trigger Reanimated's mapper system
        global.__callMicrotasks();
      });
    })();

    return () => {
      property.removeListeners();
    };
  }, [property, sharedValue]);
}

Key Requirements

  1. installWorkletDispatcher(runOnUI) - Must be called once at app startup to install Nitro's dispatcher on Reanimated's UI runtime
  2. NitroModules.box(property) - Box the HybridObject so it can be accessed from a worklet on the UI thread
  3. runOnUI() - Run the listener setup on Reanimated's UI thread
  4. global.__callMicrotasks() - Must be called after setting SharedValue to trigger Reanimated's mapper system

Why This Works

  • The listener callback runs on the UI thread, not the JS thread
  • Even when JS is blocked (busy loop, heavy computation), the UI thread keeps running
  • Rive graphics runs on native/UI thread, so it keeps animating
  • The listener receives updates and sets SharedValue on UI thread
  • __callMicrotasks() ensures Reanimated processes the update immediately

@mfazekas mfazekas force-pushed the feat/rive-state-change-listener branch from 1603a37 to 28be888 Compare December 22, 2025 13:09
@mfazekas mfazekas changed the title feat(example): Rive to Reanimated shared value demo feat: UI thread listener support for Reanimated shared values Dec 22, 2025
@mfazekas mfazekas force-pushed the feat/rive-state-change-listener branch 2 times, most recently from 20659c9 to bbe54c2 Compare December 22, 2025 13:52
@mfazekas mfazekas marked this pull request as ready for review December 22, 2025 13:57
@mfazekas mfazekas force-pushed the feat/rive-state-change-listener branch from bbe54c2 to 22193fc Compare January 8, 2026 16:45
@mfazekas mfazekas force-pushed the feat/rive-state-change-listener branch from 22193fc to 0c28302 Compare January 22, 2026 05:20
Demonstrates Rive animation driving React Native UI via data binding listeners.
Add RiveWorkletBridge HybridObject that installs Nitro's dispatcher on
Reanimated's UI runtime, enabling ViewModel property listeners to run on
the UI thread and update SharedValues without blocking when JS is busy.

- iOS: Uses GCD dispatch_async/dispatch_sync to main queue
- Android: Uses Handler(Looper.getMainLooper()) via JNI bridge
- Export installWorkletDispatcher() function from package
- Update example to demonstrate JS thread blocking test
@mfazekas mfazekas force-pushed the feat/rive-state-change-listener branch from 0c28302 to 2b333fe Compare January 22, 2026 05:22
Update react-native-reanimated to 4.2.1 and fix metro config variable reference.
@mfazekas mfazekas enabled auto-merge (squash) January 22, 2026 07:33
@mfazekas mfazekas requested a review from HayesGordon January 22, 2026 07:33
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.

2 participants