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 demonstrates JS thread blocking test
Screen.Recording.2025-12-11.at.10.23.13.mov

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

Demonstrates Rive animation driving React Native UI via data binding listeners.
@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 from 28be888 to 20659c9 Compare December 22, 2025 13:16
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 20659c9 to bbe54c2 Compare December 22, 2025 13:52
@mfazekas mfazekas marked this pull request as ready for review December 22, 2025 13:57
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