From e73e81c51794e97657a935d89c10c46bf25f1687 Mon Sep 17 00:00:00 2001 From: osdnk Date: Sat, 5 Jan 2019 16:43:28 +0100 Subject: [PATCH 1/6] Add listeners handling for not-value animated nodes as well --- .../Animated/src/__tests__/Animated-test.js | 41 +++++++++ Libraries/Animated/src/nodes/AnimatedNode.js | 87 +++++++++++++++++++ Libraries/Animated/src/nodes/AnimatedValue.js | 87 ++----------------- .../src/nodes/AnimatedWithChildren.js | 12 +++ 4 files changed, 145 insertions(+), 82 deletions(-) diff --git a/Libraries/Animated/src/__tests__/Animated-test.js b/Libraries/Animated/src/__tests__/Animated-test.js index 930d8a020f2bff..eb527fcc2c736a 100644 --- a/Libraries/Animated/src/__tests__/Animated-test.js +++ b/Libraries/Animated/src/__tests__/Animated-test.js @@ -803,6 +803,47 @@ describe('Animated tests', () => { expect(value1.__getValue()).toBe(1492); }); + it('should get updates for derived animated nodes', () => { + const value1 = new Animated.Value(40); + const value2 = new Animated.Value(50); + const value3 = new Animated.Value(0); + const value4 = Animated.add(value3, Animated.multiply(value1, value2)); + const callback = jest.fn(); + const view = new Animated.__PropsOnlyForTests( + { + style: { + transform: [ + { + translateX: value4, + }, + ], + }, + }, + callback, + ); + const listener = jest.fn(); + const id = value4.addListener(listener); + value3.setValue(137); + expect(listener.mock.calls.length).toBe(1); + expect(listener).toBeCalledWith({value: 2137}); + value1.setValue(0); + expect(listener.mock.calls.length).toBe(2); + expect(listener).toBeCalledWith({value: 137}); + expect(view.__getValue()).toEqual({ + style: { + transform: [ + { + translateX: 137, + }, + ], + }, + }); + value4.removeListener(id); + value1.setValue(40); + expect(listener.mock.calls.length).toBe(2); + expect(value4.__getValue()).toBe(2137); + }); + it('should removeAll', () => { const value1 = new Animated.Value(0); const listener = jest.fn(); diff --git a/Libraries/Animated/src/nodes/AnimatedNode.js b/Libraries/Animated/src/nodes/AnimatedNode.js index 1d670f18a159c9..798c51d12ace89 100644 --- a/Libraries/Animated/src/nodes/AnimatedNode.js +++ b/Libraries/Animated/src/nodes/AnimatedNode.js @@ -11,8 +11,11 @@ const NativeAnimatedHelper = require('../NativeAnimatedHelper'); +const NativeAnimatedAPI = NativeAnimatedHelper.API; const invariant = require('invariant'); +let _uniqueId = 1; + // Note(vjeux): this would be better as an interface but flow doesn't // support them yet class AnimatedNode { @@ -32,6 +35,7 @@ class AnimatedNode { __getChildren(): Array { return []; } + _listeners = {}; /* Methods and props used by native Animated impl */ __isNative: boolean; @@ -40,7 +44,90 @@ class AnimatedNode { if (!this.__isNative) { throw new Error('This node cannot be made a "native" animated node'); } + + if (Object.keys(this._listeners).length) { + this._startListeningToNativeValueUpdates(); + } + } + + /** + * Adds an asynchronous listener to the value so you can observe updates from + * animations. This is useful because there is no way to + * synchronously read the value because it might be driven natively. + * + * See http://facebook.github.io/react-native/docs/animatedvalue.html#addlistener + */ + addListener(callback: ValueListenerCallback): string { + const id = String(_uniqueId++); + this._listeners[id] = callback; + if (this.__isNative) { + this._startListeningToNativeValueUpdates(); + } + return id; + } + + /** + * Unregister a listener. The `id` param shall match the identifier + * previously returned by `addListener()`. + * + * See http://facebook.github.io/react-native/docs/animatedvalue.html#removelistener + */ + removeListener(id: string): void { + delete this._listeners[id]; + if (this.__isNative && Object.keys(this._listeners).length === 0) { + this._stopListeningForNativeValueUpdates(); + } } + + /** + * Remove all registered listeners. + * + * See http://facebook.github.io/react-native/docs/animatedvalue.html#removealllisteners + */ + removeAllListeners(): void { + this._listeners = {}; + if (this.__isNative) { + this._stopListeningForNativeValueUpdates(); + } + } + + _startListeningToNativeValueUpdates() { + if (this.__nativeAnimatedValueListener) { + return; + } + + NativeAnimatedAPI.startListeningToAnimatedNodeValue(this.__getNativeTag()); + this.__nativeAnimatedValueListener = NativeAnimatedHelper.nativeEventEmitter.addListener( + 'onAnimatedValueUpdate', + data => { + if (data.tag !== this.__getNativeTag()) { + return; + } + this._onAnimatedValueUpdateReceived(data.value); + }, + ); + } + + _onAnimatedValueUpdateReceived(value: number) { + this._callListeners(value); + } + + _callListeners(value: number): void { + for (const key in this._listeners) { + this._listeners[key]({value}); + } + } + + _stopListeningForNativeValueUpdates() { + if (!this.__nativeAnimatedValueListener) { + return; + } + + this.__nativeAnimatedValueListener.remove(); + this.__nativeAnimatedValueListener = null; + NativeAnimatedAPI.stopListeningToAnimatedNodeValue(this.__getNativeTag()); + } + __getNativeTag(): ?number { NativeAnimatedHelper.assertNativeAnimatedModule(); invariant( diff --git a/Libraries/Animated/src/nodes/AnimatedValue.js b/Libraries/Animated/src/nodes/AnimatedValue.js index e896f660000ab2..3826ba5dec817a 100644 --- a/Libraries/Animated/src/nodes/AnimatedValue.js +++ b/Libraries/Animated/src/nodes/AnimatedValue.js @@ -22,8 +22,6 @@ const NativeAnimatedAPI = NativeAnimatedHelper.API; type ValueListenerCallback = (state: {value: number}) => void; -let _uniqueId = 1; - /** * Animated works by building a directed acyclic graph of dependencies * transparently when you render your Animated components. @@ -85,7 +83,6 @@ class AnimatedValue extends AnimatedWithChildren { this._startingValue = this._value = value; this._offset = 0; this._animation = null; - this._listeners = {}; } __detach() { @@ -97,14 +94,6 @@ class AnimatedValue extends AnimatedWithChildren { return this._value + this._offset; } - __makeNative() { - super.__makeNative(); - - if (Object.keys(this._listeners).length) { - this._startListeningToNativeValueUpdates(); - } - } - /** * Directly set the value. This will stop any animations running on the value * and update all the bound properties. @@ -167,74 +156,6 @@ class AnimatedValue extends AnimatedWithChildren { } } - /** - * Adds an asynchronous listener to the value so you can observe updates from - * animations. This is useful because there is no way to - * synchronously read the value because it might be driven natively. - * - * See http://facebook.github.io/react-native/docs/animatedvalue.html#addlistener - */ - addListener(callback: ValueListenerCallback): string { - const id = String(_uniqueId++); - this._listeners[id] = callback; - if (this.__isNative) { - this._startListeningToNativeValueUpdates(); - } - return id; - } - - /** - * Unregister a listener. The `id` param shall match the identifier - * previously returned by `addListener()`. - * - * See http://facebook.github.io/react-native/docs/animatedvalue.html#removelistener - */ - removeListener(id: string): void { - delete this._listeners[id]; - if (this.__isNative && Object.keys(this._listeners).length === 0) { - this._stopListeningForNativeValueUpdates(); - } - } - - /** - * Remove all registered listeners. - * - * See http://facebook.github.io/react-native/docs/animatedvalue.html#removealllisteners - */ - removeAllListeners(): void { - this._listeners = {}; - if (this.__isNative) { - this._stopListeningForNativeValueUpdates(); - } - } - - _startListeningToNativeValueUpdates() { - if (this.__nativeAnimatedValueListener) { - return; - } - - NativeAnimatedAPI.startListeningToAnimatedNodeValue(this.__getNativeTag()); - this.__nativeAnimatedValueListener = NativeAnimatedHelper.nativeEventEmitter.addListener( - 'onAnimatedValueUpdate', - data => { - if (data.tag !== this.__getNativeTag()) { - return; - } - this._updateValue(data.value, false /* flush */); - }, - ); - } - - _stopListeningForNativeValueUpdates() { - if (!this.__nativeAnimatedValueListener) { - return; - } - - this.__nativeAnimatedValueListener.remove(); - this.__nativeAnimatedValueListener = null; - NativeAnimatedAPI.stopListeningToAnimatedNodeValue(this.__getNativeTag()); - } - /** * Stops any running animation or tracking. `callback` is invoked with the * final value after stopping the animation, which is useful for updating @@ -259,6 +180,10 @@ class AnimatedValue extends AnimatedWithChildren { this._value = this._startingValue; } + _onAnimatedValueUpdateReceived(value) { + this._updateValue(value, false /*flush*/); + } + /** * Interpolates the value before updating the property, e.g. mapping 0-1 to * 0-10. @@ -321,9 +246,7 @@ class AnimatedValue extends AnimatedWithChildren { if (flush) { _flush(this); } - for (const key in this._listeners) { - this._listeners[key]({value: this.__getValue()}); - } + this._callListeners(this.__getValue()); } __getNativeConfig(): Object { diff --git a/Libraries/Animated/src/nodes/AnimatedWithChildren.js b/Libraries/Animated/src/nodes/AnimatedWithChildren.js index 3940c4ae206082..af568d49dfa9b2 100644 --- a/Libraries/Animated/src/nodes/AnimatedWithChildren.js +++ b/Libraries/Animated/src/nodes/AnimatedWithChildren.js @@ -31,6 +31,7 @@ class AnimatedWithChildren extends AnimatedNode { ); } } + super.__makeNative(); } __addChild(child: AnimatedNode): void { @@ -69,6 +70,17 @@ class AnimatedWithChildren extends AnimatedNode { __getChildren(): Array { return this._children; } + + _callListeners(value: number): void { + super._callListeners(value); + if (!this.__isNative) { + for (const child of this._children) { + if (child.__getValue) { + child._callListeners(child.__getValue()); + } + } + } + } } module.exports = AnimatedWithChildren; From fb301f8c148f9e23a88148847346c54f9ae96ffb Mon Sep 17 00:00:00 2001 From: osdnk Date: Sun, 6 Jan 2019 02:28:56 +0100 Subject: [PATCH 2/6] Fix types --- Libraries/Animated/src/nodes/AnimatedNode.js | 10 +++++++++- Libraries/Animated/src/nodes/AnimatedValue.js | 4 ---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Libraries/Animated/src/nodes/AnimatedNode.js b/Libraries/Animated/src/nodes/AnimatedNode.js index 798c51d12ace89..b2b06af4f8e32c 100644 --- a/Libraries/Animated/src/nodes/AnimatedNode.js +++ b/Libraries/Animated/src/nodes/AnimatedNode.js @@ -14,11 +14,15 @@ const NativeAnimatedHelper = require('../NativeAnimatedHelper'); const NativeAnimatedAPI = NativeAnimatedHelper.API; const invariant = require('invariant'); +type ValueListenerCallback = (state: {value: number}) => void; + let _uniqueId = 1; // Note(vjeux): this would be better as an interface but flow doesn't // support them yet class AnimatedNode { + _listeners: {[key: string]: ValueListenerCallback}; + __nativeAnimatedValueListener: ?any; __attach(): void {} __detach(): void { if (this.__isNative && this.__nativeTag != null) { @@ -35,11 +39,15 @@ class AnimatedNode { __getChildren(): Array { return []; } - _listeners = {}; /* Methods and props used by native Animated impl */ __isNative: boolean; __nativeTag: ?number; + + constructor() { + this._listeners = {}; + } + __makeNative() { if (!this.__isNative) { throw new Error('This node cannot be made a "native" animated node'); diff --git a/Libraries/Animated/src/nodes/AnimatedValue.js b/Libraries/Animated/src/nodes/AnimatedValue.js index 3826ba5dec817a..f1207891f175d6 100644 --- a/Libraries/Animated/src/nodes/AnimatedValue.js +++ b/Libraries/Animated/src/nodes/AnimatedValue.js @@ -20,8 +20,6 @@ import type AnimatedTracking from './AnimatedTracking'; const NativeAnimatedAPI = NativeAnimatedHelper.API; -type ValueListenerCallback = (state: {value: number}) => void; - /** * Animated works by building a directed acyclic graph of dependencies * transparently when you render your Animated components. @@ -75,8 +73,6 @@ class AnimatedValue extends AnimatedWithChildren { _offset: number; _animation: ?Animation; _tracking: ?AnimatedTracking; - _listeners: {[key: string]: ValueListenerCallback}; - __nativeAnimatedValueListener: ?any; constructor(value: number) { super(); From 3537e56970eba449a7795d81b6c2a04cc8695786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Osadnik?= Date: Sun, 6 Jan 2019 14:54:35 +0100 Subject: [PATCH 3/6] Add missing type --- Libraries/Animated/src/nodes/AnimatedValue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Animated/src/nodes/AnimatedValue.js b/Libraries/Animated/src/nodes/AnimatedValue.js index f1207891f175d6..ce944aee369a1f 100644 --- a/Libraries/Animated/src/nodes/AnimatedValue.js +++ b/Libraries/Animated/src/nodes/AnimatedValue.js @@ -176,7 +176,7 @@ class AnimatedValue extends AnimatedWithChildren { this._value = this._startingValue; } - _onAnimatedValueUpdateReceived(value) { + _onAnimatedValueUpdateReceived(value: number): void { this._updateValue(value, false /*flush*/); } From 9f3f01e06029edd7c7f01062a625faa0fae3d885 Mon Sep 17 00:00:00 2001 From: osdnk Date: Sat, 16 Feb 2019 13:14:58 +0100 Subject: [PATCH 4/6] Fix flow warns --- Libraries/Animated/src/nodes/AnimatedNode.js | 2 +- Libraries/Animated/src/nodes/AnimatedValue.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/Animated/src/nodes/AnimatedNode.js b/Libraries/Animated/src/nodes/AnimatedNode.js index b2b06af4f8e32c..73aa8c7434e00c 100644 --- a/Libraries/Animated/src/nodes/AnimatedNode.js +++ b/Libraries/Animated/src/nodes/AnimatedNode.js @@ -65,7 +65,7 @@ class AnimatedNode { * * See http://facebook.github.io/react-native/docs/animatedvalue.html#addlistener */ - addListener(callback: ValueListenerCallback): string { + addListener(callback: any): string { const id = String(_uniqueId++); this._listeners[id] = callback; if (this.__isNative) { diff --git a/Libraries/Animated/src/nodes/AnimatedValue.js b/Libraries/Animated/src/nodes/AnimatedValue.js index ce944aee369a1f..7813778407e440 100644 --- a/Libraries/Animated/src/nodes/AnimatedValue.js +++ b/Libraries/Animated/src/nodes/AnimatedValue.js @@ -242,7 +242,7 @@ class AnimatedValue extends AnimatedWithChildren { if (flush) { _flush(this); } - this._callListeners(this.__getValue()); + super._callListeners(this.__getValue()); } __getNativeConfig(): Object { From 5f8931e7ce92c6c60c0b0ac86e45f455cf4e867c Mon Sep 17 00:00:00 2001 From: osdnk Date: Sat, 16 Feb 2019 13:46:47 +0100 Subject: [PATCH 5/6] Enhance flow types --- Libraries/Animated/src/nodes/AnimatedNode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Animated/src/nodes/AnimatedNode.js b/Libraries/Animated/src/nodes/AnimatedNode.js index 73aa8c7434e00c..9ac9206afcb8d8 100644 --- a/Libraries/Animated/src/nodes/AnimatedNode.js +++ b/Libraries/Animated/src/nodes/AnimatedNode.js @@ -65,7 +65,7 @@ class AnimatedNode { * * See http://facebook.github.io/react-native/docs/animatedvalue.html#addlistener */ - addListener(callback: any): string { + addListener(callback: (value: any) => void): string { const id = String(_uniqueId++); this._listeners[id] = callback; if (this.__isNative) { From 196859c2069928cfeb2c8fe0d15ffd9ef0e2e2af Mon Sep 17 00:00:00 2001 From: osdnk Date: Sat, 16 Feb 2019 14:02:17 +0100 Subject: [PATCH 6/6] void -> mixed --- Libraries/Animated/src/nodes/AnimatedNode.js | 4 ++-- Libraries/Animated/src/nodes/AnimatedValueXY.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Libraries/Animated/src/nodes/AnimatedNode.js b/Libraries/Animated/src/nodes/AnimatedNode.js index 9ac9206afcb8d8..00c6dd62f05c3a 100644 --- a/Libraries/Animated/src/nodes/AnimatedNode.js +++ b/Libraries/Animated/src/nodes/AnimatedNode.js @@ -14,7 +14,7 @@ const NativeAnimatedHelper = require('../NativeAnimatedHelper'); const NativeAnimatedAPI = NativeAnimatedHelper.API; const invariant = require('invariant'); -type ValueListenerCallback = (state: {value: number}) => void; +type ValueListenerCallback = (state: {value: number}) => mixed; let _uniqueId = 1; @@ -65,7 +65,7 @@ class AnimatedNode { * * See http://facebook.github.io/react-native/docs/animatedvalue.html#addlistener */ - addListener(callback: (value: any) => void): string { + addListener(callback: (value: any) => mixed): string { const id = String(_uniqueId++); this._listeners[id] = callback; if (this.__isNative) { diff --git a/Libraries/Animated/src/nodes/AnimatedValueXY.js b/Libraries/Animated/src/nodes/AnimatedValueXY.js index adc7a526a1eaa8..15575c7e5f944f 100644 --- a/Libraries/Animated/src/nodes/AnimatedValueXY.js +++ b/Libraries/Animated/src/nodes/AnimatedValueXY.js @@ -14,7 +14,7 @@ const AnimatedWithChildren = require('./AnimatedWithChildren'); const invariant = require('invariant'); -type ValueXYListenerCallback = (value: {x: number, y: number}) => void; +type ValueXYListenerCallback = (value: {x: number, y: number}) => mixed; let _uniqueId = 1;