From 05e380f26c65fc3ee35d859bdc509720491e5843 Mon Sep 17 00:00:00 2001 From: Moti Zilberman Date: Tue, 12 Jan 2021 11:12:08 -0800 Subject: [PATCH 1/2] Type AsyncStorage Summary: Types the AsyncStorage module with Flow. Changelog: [Internal] Reviewed By: MichaReiser Differential Revision: D25884607 fbshipit-source-id: 7762ebfc68b87e6c5a0d8100fcc564f002061f5c --- IntegrationTests/AsyncStorageTest.js | 50 +++++--- Libraries/Storage/AsyncStorage.js | 114 ++++++++++-------- Libraries/Storage/NativeAsyncLocalStorage.js | 17 +-- .../Storage/NativeAsyncSQLiteDBStorage.js | 17 +-- .../js/utils/RNTesterStatePersister.js | 6 +- 5 files changed, 128 insertions(+), 76 deletions(-) diff --git a/IntegrationTests/AsyncStorageTest.js b/IntegrationTests/AsyncStorageTest.js index 12d9f3d620f331..a6039d4b6d571d 100644 --- a/IntegrationTests/AsyncStorageTest.js +++ b/IntegrationTests/AsyncStorageTest.js @@ -16,6 +16,7 @@ const {AsyncStorage, Text, View, StyleSheet} = ReactNative; const {TestModule} = ReactNative.NativeModules; const deepDiffer = require('react-native/Libraries/Utilities/differ/deepDiffer'); +const nullthrows = require('nullthrows'); const DEBUG = false; @@ -43,15 +44,32 @@ function expectTrue(condition: boolean, message: string) { } } +// Type-safe wrapper around JSON.stringify +function stringify( + value: + | void + | null + | string + | number + | boolean + | {...} + | $ReadOnlyArray, +): string { + if (typeof value === 'undefined') { + return 'undefined'; + } + return JSON.stringify(value); +} + function expectEqual(lhs, rhs, testname: string) { expectTrue( !deepDiffer(lhs, rhs), 'Error in test ' + testname + ': expected\n' + - JSON.stringify(rhs) + + stringify(rhs) + '\ngot\n' + - JSON.stringify(lhs), + stringify(lhs), ); } @@ -61,7 +79,7 @@ function expectAsyncNoError(place, err) { } expectTrue( err === null, - 'Unexpected error in ' + place + ': ' + JSON.stringify(err), + 'Unexpected error in ' + place + ': ' + stringify(err), ); } @@ -71,7 +89,7 @@ function testSetAndGet() { AsyncStorage.getItem(KEY_1, (err2, result) => { expectAsyncNoError('testSetAndGet/getItem', err2); expectEqual(result, VAL_1, 'testSetAndGet setItem'); - updateMessage('get(key_1) correctly returned ' + result); + updateMessage('get(key_1) correctly returned ' + String(result)); runTestCase('should get null for missing key', testMissingGet); }); }); @@ -81,7 +99,7 @@ function testMissingGet() { AsyncStorage.getItem(KEY_2, (err, result) => { expectAsyncNoError('testMissingGet/setItem', err); expectEqual(result, null, 'testMissingGet'); - updateMessage('missing get(key_2) correctly returned ' + result); + updateMessage('missing get(key_2) correctly returned ' + String(result)); runTestCase('check set twice results in a single key', testSetTwice); }); } @@ -105,8 +123,9 @@ function testRemoveItem() { AsyncStorage.getAllKeys((err, result) => { expectAsyncNoError('testRemoveItem/getAllKeys', err); expectTrue( - result.indexOf(KEY_1) >= 0 && result.indexOf(KEY_2) >= 0, - 'Missing KEY_1 or KEY_2 in ' + '(' + result + ')', + nullthrows(result).indexOf(KEY_1) >= 0 && + nullthrows(result).indexOf(KEY_2) >= 0, + 'Missing KEY_1 or KEY_2 in ' + '(' + nullthrows(result).join() + ')', ); updateMessage('testRemoveItem - add two items'); AsyncStorage.removeItem(KEY_1, err2 => { @@ -123,8 +142,8 @@ function testRemoveItem() { AsyncStorage.getAllKeys((err4, result3) => { expectAsyncNoError('testRemoveItem/getAllKeys', err4); expectTrue( - result3.indexOf(KEY_1) === -1, - 'Unexpected: KEY_1 present in ' + result3, + nullthrows(result3).indexOf(KEY_1) === -1, + 'Unexpected: KEY_1 present in ' + nullthrows(result3).join(), ); updateMessage('proper length returned.'); runTestCase('should merge values', testMerge); @@ -137,13 +156,17 @@ function testRemoveItem() { } function testMerge() { - AsyncStorage.setItem(KEY_MERGE, JSON.stringify(VAL_MERGE_1), err1 => { + AsyncStorage.setItem(KEY_MERGE, stringify(VAL_MERGE_1), err1 => { expectAsyncNoError('testMerge/setItem', err1); - AsyncStorage.mergeItem(KEY_MERGE, JSON.stringify(VAL_MERGE_2), err2 => { + AsyncStorage.mergeItem(KEY_MERGE, stringify(VAL_MERGE_2), err2 => { expectAsyncNoError('testMerge/mergeItem', err2); AsyncStorage.getItem(KEY_MERGE, (err3, result) => { expectAsyncNoError('testMerge/setItem', err3); - expectEqual(JSON.parse(result), VAL_MERGE_EXPECT, 'testMerge'); + expectEqual( + JSON.parse(nullthrows(result)), + VAL_MERGE_EXPECT, + 'testMerge', + ); updateMessage('objects deeply merged\nDone!'); runTestCase('multi set and get', testOptimizedMultiGet); }); @@ -165,8 +188,7 @@ function testOptimizedMultiGet() { expectAsyncNoError(`${i} testOptimizedMultiGet/multiGet`, err2); expectEqual(result, batch, `${i} testOptimizedMultiGet multiGet`); updateMessage( - 'multiGet([key_1, key_2]) correctly returned ' + - JSON.stringify(result), + 'multiGet([key_1, key_2]) correctly returned ' + stringify(result), ); done(); }); diff --git a/Libraries/Storage/AsyncStorage.js b/Libraries/Storage/AsyncStorage.js index d1fd9c23f54237..a4fcfd70a98411 100644 --- a/Libraries/Storage/AsyncStorage.js +++ b/Libraries/Storage/AsyncStorage.js @@ -5,8 +5,7 @@ * LICENSE file in the root directory of this source tree. * * @format - * @noflow - * @flow-weak + * @flow strict * @jsdoc */ @@ -19,6 +18,20 @@ import invariant from 'invariant'; // Use SQLite if available, otherwise file storage. const RCTAsyncStorage = NativeAsyncSQLiteDBStorage || NativeAsyncLocalStorage; +type GetRequest = { + keys: Array, + callback: ?(errors: ?Array, result: ?Array>) => void, + keyIndex: number, + resolve: ( + result?: + | void + | null + | Promise>> + | Array>, + ) => void, + reject: (error?: mixed) => void, +}; + /** * `AsyncStorage` is a simple, unencrypted, asynchronous, persistent, key-value * storage system that is global to the app. It should be used instead of @@ -27,7 +40,7 @@ const RCTAsyncStorage = NativeAsyncSQLiteDBStorage || NativeAsyncLocalStorage; * See https://reactnative.dev/docs/asyncstorage.html */ const AsyncStorage = { - _getRequests: ([]: Array), + _getRequests: ([]: Array), _getKeys: ([]: Array), _immediate: (null: ?number), @@ -39,7 +52,7 @@ const AsyncStorage = { getItem: function( key: string, callback?: ?(error: ?Error, result: ?string) => void, - ): Promise { + ): Promise { invariant(RCTAsyncStorage, 'RCTAsyncStorage not available'); return new Promise((resolve, reject) => { RCTAsyncStorage.multiGet([key], function(errors, result) { @@ -65,7 +78,7 @@ const AsyncStorage = { key: string, value: string, callback?: ?(error: ?Error) => void, - ): Promise { + ): Promise { invariant(RCTAsyncStorage, 'RCTAsyncStorage not available'); return new Promise((resolve, reject) => { RCTAsyncStorage.multiSet([[key, value]], function(errors) { @@ -74,7 +87,7 @@ const AsyncStorage = { if (errs) { reject(errs[0]); } else { - resolve(null); + resolve(); } }); }); @@ -88,7 +101,7 @@ const AsyncStorage = { removeItem: function( key: string, callback?: ?(error: ?Error) => void, - ): Promise { + ): Promise { invariant(RCTAsyncStorage, 'RCTAsyncStorage not available'); return new Promise((resolve, reject) => { RCTAsyncStorage.multiRemove([key], function(errors) { @@ -97,7 +110,7 @@ const AsyncStorage = { if (errs) { reject(errs[0]); } else { - resolve(null); + resolve(); } }); }); @@ -115,7 +128,7 @@ const AsyncStorage = { key: string, value: string, callback?: ?(error: ?Error) => void, - ): Promise { + ): Promise { invariant(RCTAsyncStorage, 'RCTAsyncStorage not available'); return new Promise((resolve, reject) => { RCTAsyncStorage.multiMerge([[key, value]], function(errors) { @@ -124,7 +137,7 @@ const AsyncStorage = { if (errs) { reject(errs[0]); } else { - resolve(null); + resolve(); } }); }); @@ -137,7 +150,7 @@ const AsyncStorage = { * * See https://reactnative.dev/docs/asyncstorage.html#clear */ - clear: function(callback?: ?(error: ?Error) => void): Promise { + clear: function(callback?: ?(error: ?Error) => void): Promise { invariant(RCTAsyncStorage, 'RCTAsyncStorage not available'); return new Promise((resolve, reject) => { RCTAsyncStorage.clear(function(error) { @@ -145,7 +158,7 @@ const AsyncStorage = { if (error && convertError(error)) { reject(convertError(error)); } else { - resolve(null); + resolve(); } }); }); @@ -158,7 +171,7 @@ const AsyncStorage = { */ getAllKeys: function( callback?: ?(error: ?Error, keys: ?Array) => void, - ): Promise { + ): Promise> { invariant(RCTAsyncStorage, 'RCTAsyncStorage not available'); return new Promise((resolve, reject) => { RCTAsyncStorage.getAllKeys(function(error, keys) { @@ -229,7 +242,7 @@ const AsyncStorage = { multiGet: function( keys: Array, callback?: ?(errors: ?Array, result: ?Array>) => void, - ): Promise { + ): Promise>> { if (!this._immediate) { this._immediate = setImmediate(() => { this._immediate = null; @@ -237,29 +250,22 @@ const AsyncStorage = { }); } - const getRequest = { - keys: keys, - callback: callback, - // do we need this? - keyIndex: this._getKeys.length, - resolve: null, - reject: null, - }; - - const promiseResult = new Promise((resolve, reject) => { - getRequest.resolve = resolve; - getRequest.reject = reject; - }); - - this._getRequests.push(getRequest); - // avoid fetching duplicates - keys.forEach(key => { - if (this._getKeys.indexOf(key) === -1) { - this._getKeys.push(key); - } + return new Promise>>((resolve, reject) => { + this._getRequests.push({ + keys, + callback, + // do we need this? + keyIndex: this._getKeys.length, + resolve, + reject, + }); + // avoid fetching duplicates + keys.forEach(key => { + if (this._getKeys.indexOf(key) === -1) { + this._getKeys.push(key); + } + }); }); - - return promiseResult; }, /** @@ -271,7 +277,7 @@ const AsyncStorage = { multiSet: function( keyValuePairs: Array>, callback?: ?(errors: ?Array) => void, - ): Promise { + ): Promise { invariant(RCTAsyncStorage, 'RCTAsyncStorage not available'); return new Promise((resolve, reject) => { RCTAsyncStorage.multiSet(keyValuePairs, function(errors) { @@ -280,7 +286,7 @@ const AsyncStorage = { if (error) { reject(error); } else { - resolve(null); + resolve(); } }); }); @@ -294,7 +300,7 @@ const AsyncStorage = { multiRemove: function( keys: Array, callback?: ?(errors: ?Array) => void, - ): Promise { + ): Promise { invariant(RCTAsyncStorage, 'RCTAsyncStorage not available'); return new Promise((resolve, reject) => { RCTAsyncStorage.multiRemove(keys, function(errors) { @@ -303,7 +309,7 @@ const AsyncStorage = { if (error) { reject(error); } else { - resolve(null); + resolve(); } }); }); @@ -320,7 +326,7 @@ const AsyncStorage = { multiMerge: function( keyValuePairs: Array>, callback?: ?(errors: ?Array) => void, - ): Promise { + ): Promise { invariant(RCTAsyncStorage, 'RCTAsyncStorage not available'); return new Promise((resolve, reject) => { RCTAsyncStorage.multiMerge(keyValuePairs, function(errors) { @@ -329,7 +335,7 @@ const AsyncStorage = { if (error) { reject(error); } else { - resolve(null); + resolve(); } }); }); @@ -337,24 +343,38 @@ const AsyncStorage = { }; // Not all native implementations support merge. -if (!RCTAsyncStorage.multiMerge) { - delete AsyncStorage.mergeItem; - delete AsyncStorage.multiMerge; +// TODO: Check whether above comment is correct. multiMerge is guaranteed to +// exist in the module spec so we should be able to just remove this check. +if (RCTAsyncStorage && !RCTAsyncStorage.multiMerge) { + // $FlowFixMe[unclear-type] + delete (AsyncStorage: any).mergeItem; + // $FlowFixMe[unclear-type] + delete (AsyncStorage: any).multiMerge; } -function convertErrors(errs) { +function convertErrors( + // NOTE: The native module spec only has the Array case, but the Android + // implementation passes a single object. + errs: ?( + | {message: string, key?: string} + | Array<{message: string, key?: string}> + ), +) { if (!errs) { return null; } return (Array.isArray(errs) ? errs : [errs]).map(e => convertError(e)); } +declare function convertError(void | null): null; +declare function convertError({message: string, key?: string}): Error; function convertError(error) { if (!error) { return null; } const out = new Error(error.message); - out.key = error.key; // flow doesn't like this :( + // $FlowFixMe[unclear-type] + (out: any).key = error.key; return out; } diff --git a/Libraries/Storage/NativeAsyncLocalStorage.js b/Libraries/Storage/NativeAsyncLocalStorage.js index 834cd97f3594b2..39502dde329545 100644 --- a/Libraries/Storage/NativeAsyncLocalStorage.js +++ b/Libraries/Storage/NativeAsyncLocalStorage.js @@ -14,29 +14,32 @@ import type {TurboModule} from '../TurboModule/RCTExport'; import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +getConstants: () => {||}; + +getConstants: () => {}; +multiGet: ( keys: Array, callback: ( - errors: ?Array<{|message: string|}>, + errors: ?Array<{message: string, key?: string}>, kvPairs: ?Array>, ) => void, ) => void; +multiSet: ( kvPairs: Array>, - callback: (errors: ?Array<{|message: string|}>) => void, + callback: (errors: ?Array<{message: string, key?: string}>) => void, ) => void; +multiMerge: ( kvPairs: Array>, - callback: (errors: ?Array<{|message: string|}>) => void, + callback: (errors: ?Array<{message: string, key?: string}>) => void, ) => void; +multiRemove: ( keys: Array, - callback: (errors: ?Array<{|message: string|}>) => void, + callback: (errors: ?Array<{message: string, key?: string}>) => void, ) => void; - +clear: (callback: (error: {|message: string|}) => void) => void; + +clear: (callback: (error: {message: string, key?: string}) => void) => void; +getAllKeys: ( - callback: (error: ?{|message: string|}, allKeys: ?Array) => void, + callback: ( + error: ?{message: string, key?: string}, + allKeys: ?Array, + ) => void, ) => void; } diff --git a/Libraries/Storage/NativeAsyncSQLiteDBStorage.js b/Libraries/Storage/NativeAsyncSQLiteDBStorage.js index 6009a98d14aebf..d3431643370de4 100644 --- a/Libraries/Storage/NativeAsyncSQLiteDBStorage.js +++ b/Libraries/Storage/NativeAsyncSQLiteDBStorage.js @@ -14,29 +14,32 @@ import type {TurboModule} from '../TurboModule/RCTExport'; import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +getConstants: () => {||}; + +getConstants: () => {}; +multiGet: ( keys: Array, callback: ( - errors: ?Array<{|message: string|}>, + errors: ?Array<{message: string, key?: string}>, kvPairs: ?Array>, ) => void, ) => void; +multiSet: ( kvPairs: Array>, - callback: (errors: ?Array<{|message: string|}>) => void, + callback: (errors: ?Array<{message: string, key?: string}>) => void, ) => void; +multiMerge: ( kvPairs: Array>, - callback: (errors: ?Array<{|message: string|}>) => void, + callback: (errors: ?Array<{message: string, key?: string}>) => void, ) => void; +multiRemove: ( keys: Array, - callback: (errors: ?Array<{|message: string|}>) => void, + callback: (errors: ?Array<{message: string, key?: string}>) => void, ) => void; - +clear: (callback: (error: {|message: string|}) => void) => void; + +clear: (callback: (error: {message: string, key?: string}) => void) => void; +getAllKeys: ( - callback: (error: ?{|message: string|}, allKeys: ?Array) => void, + callback: ( + error: ?{message: string, key?: string}, + allKeys: ?Array, + ) => void, ) => void; } diff --git a/packages/rn-tester/js/utils/RNTesterStatePersister.js b/packages/rn-tester/js/utils/RNTesterStatePersister.js index 8130a69c38bba9..cc5084c6d0c8e7 100644 --- a/packages/rn-tester/js/utils/RNTesterStatePersister.js +++ b/packages/rn-tester/js/utils/RNTesterStatePersister.js @@ -59,7 +59,11 @@ function createContainer( _passSetState = (stateLamda: (state: State) => State): void => { this.setState(state => { const value = stateLamda(state.value); - AsyncStorage.setItem(this._cacheKey, JSON.stringify(value)); + AsyncStorage.setItem( + this._cacheKey, + // $FlowFixMe[incompatible-call] Error surfaced when typing AsyncStorage + JSON.stringify(value), + ); return {value}; }); }; From 6fd684150fdf631205329039fa1a82ac773d127d Mon Sep 17 00:00:00 2001 From: Joshua Gross Date: Tue, 12 Jan 2021 12:11:00 -0800 Subject: [PATCH 2/2] NativeAnimatedModule: make exceptions thrown into JS thread more verbose Summary: These methods can all throw exceptions that get caught and reported by JS. The logviews aren't currently very helpful; hopefully adding additional information will make batch debugging a little easier. Changelog: [Internal] Reviewed By: fkgozali Differential Revision: D25870788 fbshipit-source-id: a1cab225b11a3d2868f098d4575e475ee4064e65 --- .../animated/NativeAnimatedNodesManager.java | 89 +++++++++++++------ 1 file changed, 61 insertions(+), 28 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java index 9bf6d02fd2a11c..0ff510e5d0d234 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java @@ -121,7 +121,7 @@ public boolean hasActiveAnimations() { public void createAnimatedNode(int tag, ReadableMap config) { if (mAnimatedNodes.get(tag) != null) { throw new JSApplicationIllegalArgumentException( - "Animated node with tag " + tag + " already exists"); + "createAnimatedNode: Animated node [" + tag + "] already exists"); } String type = config.getString("type"); final AnimatedNode node; @@ -168,7 +168,9 @@ public void startListeningToAnimatedNodeValue(int tag, AnimatedNodeValueListener AnimatedNode node = mAnimatedNodes.get(tag); if (node == null || !(node instanceof ValueAnimatedNode)) { throw new JSApplicationIllegalArgumentException( - "Animated node with tag " + tag + " does not exist, or is not a 'value' node"); + "startListeningToAnimatedNodeValue: Animated node [" + + tag + + "] does not exist, or is not a 'value' node"); } ((ValueAnimatedNode) node).setValueListener(listener); } @@ -178,7 +180,9 @@ public void stopListeningToAnimatedNodeValue(int tag) { AnimatedNode node = mAnimatedNodes.get(tag); if (node == null || !(node instanceof ValueAnimatedNode)) { throw new JSApplicationIllegalArgumentException( - "Animated node with tag " + tag + " does not exist, or is not a 'value' node"); + "startListeningToAnimatedNodeValue: Animated node [" + + tag + + "] does not exist, or is not a 'value' node"); } ((ValueAnimatedNode) node).setValueListener(null); } @@ -188,7 +192,9 @@ public void setAnimatedNodeValue(int tag, double value) { AnimatedNode node = mAnimatedNodes.get(tag); if (node == null || !(node instanceof ValueAnimatedNode)) { throw new JSApplicationIllegalArgumentException( - "Animated node with tag " + tag + " does not exist, or is not a 'value' node"); + "setAnimatedNodeValue: Animated node [" + + tag + + "] does not exist, or is not a 'value' node"); } stopAnimationsForNode(node); ((ValueAnimatedNode) node).mValue = value; @@ -200,7 +206,9 @@ public void setAnimatedNodeOffset(int tag, double offset) { AnimatedNode node = mAnimatedNodes.get(tag); if (node == null || !(node instanceof ValueAnimatedNode)) { throw new JSApplicationIllegalArgumentException( - "Animated node with tag " + tag + " does not exist, or is not a 'value' node"); + "setAnimatedNodeOffset: Animated node [" + + tag + + "] does not exist, or is not a 'value' node"); } ((ValueAnimatedNode) node).mOffset = offset; mUpdatedNodes.put(tag, node); @@ -211,7 +219,9 @@ public void flattenAnimatedNodeOffset(int tag) { AnimatedNode node = mAnimatedNodes.get(tag); if (node == null || !(node instanceof ValueAnimatedNode)) { throw new JSApplicationIllegalArgumentException( - "Animated node with tag " + tag + " does not exist, or is not a 'value' node"); + "flattenAnimatedNodeOffset: Animated node [" + + tag + + "] does not exist, or is not a 'value' node"); } ((ValueAnimatedNode) node).flattenOffset(); } @@ -221,7 +231,9 @@ public void extractAnimatedNodeOffset(int tag) { AnimatedNode node = mAnimatedNodes.get(tag); if (node == null || !(node instanceof ValueAnimatedNode)) { throw new JSApplicationIllegalArgumentException( - "Animated node with tag " + tag + " does not exist, or is not a 'value' node"); + "extractAnimatedNodeOffset: Animated node [" + + tag + + "] does not exist, or is not a 'value' node"); } ((ValueAnimatedNode) node).extractOffset(); } @@ -232,11 +244,14 @@ public void startAnimatingNode( AnimatedNode node = mAnimatedNodes.get(animatedNodeTag); if (node == null) { throw new JSApplicationIllegalArgumentException( - "Animated node with tag " + animatedNodeTag + " does not exist"); + "startAnimatingNode: Animated node [" + animatedNodeTag + "] does not exist"); } if (!(node instanceof ValueAnimatedNode)) { throw new JSApplicationIllegalArgumentException( - "Animated node should be of type " + ValueAnimatedNode.class.getName()); + "startAnimatingNode: Animated node [" + + animatedNodeTag + + "] should be of type " + + ValueAnimatedNode.class.getName()); } final AnimationDriver existingDriver = mActiveAnimations.get(animationId); @@ -256,7 +271,8 @@ public void startAnimatingNode( } else if ("decay".equals(type)) { animation = new DecayAnimation(animationConfig); } else { - throw new JSApplicationIllegalArgumentException("Unsupported animation type: " + type); + throw new JSApplicationIllegalArgumentException( + "startAnimatingNode: Unsupported animation type [" + animatedNodeTag + "]: " + type); } animation.mId = animationId; animation.mEndCallback = endCallback; @@ -315,12 +331,16 @@ public void connectAnimatedNodes(int parentNodeTag, int childNodeTag) { AnimatedNode parentNode = mAnimatedNodes.get(parentNodeTag); if (parentNode == null) { throw new JSApplicationIllegalArgumentException( - "Animated node with tag " + parentNodeTag + " does not exists"); + "connectAnimatedNodes: Animated node with tag (parent) [" + + parentNodeTag + + "] does not exist"); } AnimatedNode childNode = mAnimatedNodes.get(childNodeTag); if (childNode == null) { throw new JSApplicationIllegalArgumentException( - "Animated node with tag " + childNodeTag + " does not exists"); + "connectAnimatedNodes: Animated node with tag (child) [" + + childNodeTag + + "] does not exist"); } parentNode.addChild(childNode); mUpdatedNodes.put(childNodeTag, childNode); @@ -330,12 +350,16 @@ public void disconnectAnimatedNodes(int parentNodeTag, int childNodeTag) { AnimatedNode parentNode = mAnimatedNodes.get(parentNodeTag); if (parentNode == null) { throw new JSApplicationIllegalArgumentException( - "Animated node with tag " + parentNodeTag + " does not exists"); + "disconnectAnimatedNodes: Animated node with tag (parent) [" + + parentNodeTag + + "] does not exist"); } AnimatedNode childNode = mAnimatedNodes.get(childNodeTag); if (childNode == null) { throw new JSApplicationIllegalArgumentException( - "Animated node with tag " + childNodeTag + " does not exists"); + "disconnectAnimatedNodes: Animated node with tag (child) [" + + childNodeTag + + "] does not exist"); } parentNode.removeChild(childNode); mUpdatedNodes.put(childNodeTag, childNode); @@ -346,17 +370,21 @@ public void connectAnimatedNodeToView(int animatedNodeTag, int viewTag) { AnimatedNode node = mAnimatedNodes.get(animatedNodeTag); if (node == null) { throw new JSApplicationIllegalArgumentException( - "Animated node with tag " + animatedNodeTag + " does not exists"); + "connectAnimatedNodeToView: Animated node with tag [" + + animatedNodeTag + + "] does not exist"); } if (!(node instanceof PropsAnimatedNode)) { throw new JSApplicationIllegalArgumentException( - "Animated node connected to view should be" - + "of type " + "connectAnimatedNodeToView: Animated node connected to view [" + + viewTag + + "] should be of type " + PropsAnimatedNode.class.getName()); } if (mReactApplicationContext == null) { throw new IllegalStateException( - "Animated node could not be connected, no ReactApplicationContext: " + viewTag); + "connectAnimatedNodeToView: Animated node could not be connected, no ReactApplicationContext: " + + viewTag); } @Nullable @@ -366,7 +394,7 @@ public void connectAnimatedNodeToView(int animatedNodeTag, int viewTag) { ReactSoftException.logSoftException( TAG, new ReactNoCrashSoftException( - "Animated node could not be connected to UIManager - uiManager disappeared for tag: " + "connectAnimatedNodeToView: Animated node could not be connected to UIManager - uiManager disappeared for tag: " + viewTag)); return; } @@ -381,12 +409,15 @@ public void disconnectAnimatedNodeFromView(int animatedNodeTag, int viewTag) { AnimatedNode node = mAnimatedNodes.get(animatedNodeTag); if (node == null) { throw new JSApplicationIllegalArgumentException( - "Animated node with tag " + animatedNodeTag + " does not exists"); + "disconnectAnimatedNodeFromView: Animated node with tag [" + + animatedNodeTag + + "] does not exist"); } if (!(node instanceof PropsAnimatedNode)) { throw new JSApplicationIllegalArgumentException( - "Animated node connected to view should be" - + "of type " + "disconnectAnimatedNodeFromView: Animated node connected to view [" + + viewTag + + "] should be of type " + PropsAnimatedNode.class.getName()); } PropsAnimatedNode propsAnimatedNode = (PropsAnimatedNode) node; @@ -398,7 +429,7 @@ public void getValue(int tag, Callback callback) { AnimatedNode node = mAnimatedNodes.get(tag); if (node == null || !(node instanceof ValueAnimatedNode)) { throw new JSApplicationIllegalArgumentException( - "Animated node with tag " + tag + " does not exists or is not a 'value' node"); + "getValue: Animated node with tag [" + tag + "] does not exist or is not a 'value' node"); } callback.invoke(((ValueAnimatedNode) node).getValue()); } @@ -415,8 +446,7 @@ public void restoreDefaultValues(int animatedNodeTag) { } if (!(node instanceof PropsAnimatedNode)) { throw new JSApplicationIllegalArgumentException( - "Animated node connected to view should be" - + "of type " + "Animated node connected to view [?] should be of type " + PropsAnimatedNode.class.getName()); } PropsAnimatedNode propsAnimatedNode = (PropsAnimatedNode) node; @@ -429,12 +459,15 @@ public void addAnimatedEventToView(int viewTag, String eventName, ReadableMap ev AnimatedNode node = mAnimatedNodes.get(nodeTag); if (node == null) { throw new JSApplicationIllegalArgumentException( - "Animated node with tag " + nodeTag + " does not exists"); + "addAnimatedEventToView: Animated node with tag [" + nodeTag + "] does not exist"); } if (!(node instanceof ValueAnimatedNode)) { throw new JSApplicationIllegalArgumentException( - "Animated node connected to event should be" - + "of type " + "addAnimatedEventToView: Animated node on view [" + + viewTag + + "] connected to event (" + + eventName + + ") should be of type " + ValueAnimatedNode.class.getName()); }