Skip to content

Commit 21c71fd

Browse files
authored
fix(android): animation freezes when file has ViewModels but no artboard default (#196)
Fixes #189. In Auto dataBind mode, Android passed `autoBind=true` to `setRiveFile` whenever `viewModelCount > 0`. If the artboard had no default ViewModel assigned, the Rive SDK threw `"No default ViewModel found for artboard"`, freezing the animation. Fix: always pass `autoBind=false` for Auto mode and let `bindToStateMachine` handle binding — it already catches `ViewModelException` gracefully when no default ViewModel exists. > **Note on the error log:** The artboard name in the error (`"iPhone 16 Pro - 3"`) is just how Rive names artboards in responsive layout files — it's not iOS code. The bug is Android-only. <details> <summary>Reproducer</summary> `.riv` file: https://rive.app/community/files/27026-50856-no-default-vm-for-artboard/ ```tsx import { RiveView, useRiveFile } from '@rive-app/react-native'; export default function Reproducer() { const { riveFile } = useRiveFile(require('./nodefaultbouncing.riv')); return riveFile ? ( <RiveView file={riveFile} autoPlay={true} // No dataBind prop — defaults to Auto. On Android (unfixed) this // triggers "No default ViewModel found for artboard" and freezes. style={{ flex: 1 }} /> ) : null; } ``` **Expected:** animation plays normally on both platforms. **Actual (Android, unfixed):** animation freezes, `ViewModelInstanceNotFound` error fired via `onError`. </details>
1 parent bad9c60 commit 21c71fd

File tree

3 files changed

+80
-1
lines changed

3 files changed

+80
-1
lines changed

android/src/main/java/com/rive/RiveReactNativeView.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class RiveReactNativeView(context: ThemedReactContext) : FrameLayout(context) {
105105
if (reload) {
106106
val hasDataBinding = when (config.bindData) {
107107
is BindData.None -> false
108-
is BindData.Auto -> config.riveFile.viewModelCount > 0
108+
is BindData.Auto -> false
109109
is BindData.Instance, is BindData.ByName -> true
110110
}
111111
riveAnimationView?.setRiveFile(

example/__tests__/autoplay.harness.tsx

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ import type { ViewModelInstance } from '@rive-app/react-native';
2121
// Source: https://rive.app/community/files/25997-48571-demo-for-tracking-rive-property-in-react-native/
2222
const BOUNCING_BALL = require('../assets/rive/bouncing_ball.riv');
2323

24+
// Simple animation with ViewModels defined but no default ViewModel assigned to the artboard
25+
// Source: https://rive.app/community/files/27026-50856-no-default-vm-for-artboard/
26+
const NO_DEFAULT_VM = require('../assets/rive/nodefaultbouncing.riv');
27+
2428
function expectDefined<T>(value: T): asserts value is NonNullable<T> {
2529
expect(value).toBeDefined();
2630
}
@@ -264,3 +268,78 @@ describe('autoPlay prop (issue #138)', () => {
264268
cleanup();
265269
});
266270
});
271+
272+
describe('Auto dataBind with no default ViewModel (issue #189)', () => {
273+
it('auto-binds default ViewModel when one exists', async () => {
274+
const file = await RiveFileFactory.fromSource(BOUNCING_BALL, undefined);
275+
276+
const context: TestContext = { ref: null, error: null };
277+
await render(
278+
<View style={{ width: 200, height: 200 }}>
279+
<RiveView
280+
hybridRef={{
281+
f: (ref: RiveViewRef | null) => {
282+
context.ref = ref;
283+
},
284+
}}
285+
style={{ flex: 1 }}
286+
file={file}
287+
autoPlay={true}
288+
fit={Fit.Contain}
289+
onError={(e) => {
290+
context.error = e.message;
291+
}}
292+
/>
293+
</View>
294+
);
295+
296+
await waitFor(
297+
() => {
298+
expect(context.ref).not.toBeNull();
299+
},
300+
{ timeout: 5000 }
301+
);
302+
303+
expect(context.error).toBeNull();
304+
305+
const vmi = context.ref!.getViewModelInstance();
306+
expect(vmi).not.toBeNull();
307+
expect(vmi!.numberProperty('ypos')).toBeDefined();
308+
309+
cleanup();
310+
});
311+
312+
it('plays without error when file has ViewModels but no artboard default', async () => {
313+
const file = await RiveFileFactory.fromSource(NO_DEFAULT_VM, undefined);
314+
315+
const context: TestContext = { ref: null, error: null };
316+
await render(
317+
<View style={{ width: 200, height: 200 }}>
318+
<RiveView
319+
hybridRef={{
320+
f: (ref: RiveViewRef | null) => {
321+
context.ref = ref;
322+
},
323+
}}
324+
style={{ flex: 1 }}
325+
file={file}
326+
autoPlay={true}
327+
fit={Fit.Contain}
328+
onError={(e) => {
329+
context.error = e.message;
330+
}}
331+
/>
332+
</View>
333+
);
334+
335+
await waitFor(
336+
() => {
337+
expect(context.ref).not.toBeNull();
338+
},
339+
{ timeout: 5000 }
340+
);
341+
342+
expect(context.error).toBeNull();
343+
cleanup();
344+
});
345+
});
319 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)