rive-react-native icon indicating copy to clipboard operation
rive-react-native copied to clipboard

React Native app crashes when calling Rive Ref Methods

Open danielcolinjames opened this issue 2 years ago • 4 comments

Description

When calling the ref functions (.play(), .reset(), etc.), it causes a crash. The crash consistently happened whether I was calling these functions from inside a useEffect, a handler function, or wherever else in the React lifecycle. It seems like there's something funky with the ref happening where the underlying Swift code is trying to do an operation on a null object or something.

The specific error is: Thread 1: EXC_BAD_ACCESS (code=1, address=0x0)

and it gets thrown by this line:

https://github.com/rive-app/rive-react-native/blob/f6432c82384aab7b4e397f819f12ff51c0c4540b/ios/RiveReactNativeView.swift#L132

Provide a Repro

Here's a code snippet that was consistently crashing the app when switching between dark and light mode in the iOS Simulator:

  const isDarkMode = useColorScheme() === 'dark'
  const animationRef = useRef<RiveRef>(null)

  useEffect(() => {
    animationRef.current?.play("Intro", LoopMode.Loop)
  }, [isDarkMode])

  return (
    <Rive
      ref={animationRef}
      alignment={Alignment.TopCenter}
      animationName="Intro"
      artboardName="Unified"
      fit={Fit.FitHeight}
      resourceName={isDarkMode ? 'OnboardingDark' : 'OnboardingLight'}
      stateMachineName="State Machine 1"
      style={animationStyles}
    />
  )

it would also crash consistently when I would navigate away from the app and back again multiple times in a row.

I tried some combination of:

  • cleanup function in useEffect to .stop() or .reset() the animation
  • .stop() or .reset() the animation each time the useEffect runs
  • calling .stop() or .reset() when the screen has focus

and eventually settled on just unmounting the component when this screen lost focus and remounting it when it gained focus again.

Source .riv/.rev file

Unfortunately I can't share the .riv file as it's from a private project. But I'm almost certain it's not specific to the Rive file since we've seen the same behavior with many different files.

Expected behavior

Rive would run the ref function(s) I call on it without crashing the app.

danielcolinjames avatar Nov 15 '22 23:11 danielcolinjames

I am experiencing the same issue. In my case I have more than one

<Rive
...
/>

component however I do not think this is contributing to the issue.

tmbradley avatar Dec 21 '22 20:12 tmbradley

We're experiencing the same issue when calling setInputState on the RiveRef. If we wrap the calls in a setTimeout (without a delay) to await pending JS runtime executions it works. In general it would be desirable to instead declaratively define the states provided for an animation since it would better fit React's paradigm.

maylukas avatar Jun 02 '23 09:06 maylukas

Completely agreed with @maylukas . However it looks like Rive always use imperative functions in other repositories too (rive-ios, rive-android) instead of declarative code, so I am not sure it will change anytime soon. For those coming here for the crash issue reported by OP, here is basically what proposes @maylukas :

  const [initialized, setInitialized] = useState(false);

  const riveRef = React.useRef<RiveRef>(null);

  useEffect(() => {
    const fnToExecute = () => {
      // Your function logic containing riveRef
      // riveRef.current.setInputState(...)
    };

    let timer;
    if (!initialized) {
      timer = setTimeout(() => {
        fnToExecute();
        setInitialized(true);
      }, 100);
    } else {
      fnToExecute();
    }

    return () => {
      clearTimeout(timer);
    };
  }, [initialized]);

PierreCapo avatar Jun 08 '23 08:06 PierreCapo

Had same issue when tried to call setInputState inside useEffect. iOS was crashing. Resolved this by using InteractionManager

Before

useEffect(() => {
      riveRef?.current?.setInputState('hero', 'health', healthValue);
}, [healthValue]);

After

const canUseRive = useRef(false);

useEffect(() => {
    InteractionManager.runAfterInteractions(() => {
      canUseRive.current = true;
    });
}, []);

useEffect(() => {
    if (canUseRive.current) {
      riveRef?.current?.setInputState('hero', 'health', healthValue);
    }
}, [healthValue]);

owystyle avatar Jul 30 '23 10:07 owystyle