From ce42e6c466a0ee12581413885d836461de86e972 Mon Sep 17 00:00:00 2001 From: Martin Booth Date: Mon, 28 Apr 2025 09:32:18 -0700 Subject: [PATCH] Sync offset value to native immediately (#50941) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/50941 Without doing this, using Animated.event to update a value with an offset causes the value to revert to not having an offset because the native side doesn't even know about the offset if it hasn't been synced. Don't think there's a better place to sync this for the cases where an animation is kicked off entirely from the native side Changelog: [Android][Fixed] - Ensure latest offset value is synced to native Reviewed By: javache Differential Revision: D73622302 --- .../Animated/__tests__/AnimatedValue-test.js | 38 +++++++++++++++++++ .../Libraries/Animated/nodes/AnimatedValue.js | 4 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedValue-test.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedValue-test.js index b78c893e2c1ff1..8e08a3ccd8bc2a 100644 --- a/packages/react-native/Libraries/Animated/__tests__/AnimatedValue-test.js +++ b/packages/react-native/Libraries/Animated/__tests__/AnimatedValue-test.js @@ -38,6 +38,7 @@ describe('AnimatedValue', () => { removeListeners: jest.fn(), startListeningToAnimatedNodeValue: jest.fn(), stopListeningToAnimatedNodeValue: jest.fn(), + extractAnimatedNodeOffset: jest.fn(), // ... }, })); @@ -49,6 +50,8 @@ describe('AnimatedValue', () => { jest.spyOn(NativeAnimatedHelper.API, 'createAnimatedNode'); jest.spyOn(NativeAnimatedHelper.API, 'dropAnimatedNode'); jest.spyOn(NativeAnimatedHelper.API, 'startListeningToAnimatedNodeValue'); + jest.spyOn(NativeAnimatedHelper.API, 'setWaitingForIdentifier'); + jest.spyOn(NativeAnimatedHelper.API, 'unsetWaitingForIdentifier'); }); it('emits update events for listeners added', () => { @@ -161,4 +164,39 @@ describe('AnimatedValue', () => { ).toBeCalledTimes(0); }); }); + + describe('when extractOffset is called', () => { + it('flushes changes to native immediately when native', () => { + const node = new AnimatedValue(0, {useNativeDriver: true}); + + expect(NativeAnimatedHelper.API.setWaitingForIdentifier).toBeCalledTimes( + 0, + ); + expect( + NativeAnimatedHelper.API.unsetWaitingForIdentifier, + ).toBeCalledTimes(0); + + node.extractOffset(); + + expect(NativeAnimatedHelper.API.setWaitingForIdentifier).toBeCalledTimes( + 1, + ); + expect( + NativeAnimatedHelper.API.unsetWaitingForIdentifier, + ).toBeCalledTimes(1); + }); + + it('does not flush changes when not native', () => { + const node = new AnimatedValue(0, {useNativeDriver: false}); + + node.extractOffset(); + + expect(NativeAnimatedHelper.API.setWaitingForIdentifier).toBeCalledTimes( + 0, + ); + expect( + NativeAnimatedHelper.API.unsetWaitingForIdentifier, + ).toBeCalledTimes(0); + }); + }); }); diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedValue.js b/packages/react-native/Libraries/Animated/nodes/AnimatedValue.js index 1727b9f463bcb5..56ee801799fd78 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedValue.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedValue.js @@ -247,7 +247,9 @@ export default class AnimatedValue extends AnimatedWithChildren { this._offset += this._value; this._value = 0; if (this.__isNative) { - NativeAnimatedAPI.extractAnimatedNodeOffset(this.__getNativeTag()); + _executeAsAnimatedBatch(this.__getNativeTag().toString(), () => + NativeAnimatedAPI.extractAnimatedNodeOffset(this.__getNativeTag()), + ); } }