React-native-reanimated: Changing style values from regular to Animated.Value removes view for several frames

Created on 24 Mar 2019  路  23Comments  路  Source: software-mansion/react-native-reanimated

Description

If you have an Animated.View with regular width: 100 style values and want to change it to an Animated.Value instance width: new Animated.Value(100), it removes the view completely from view hierarchy for several frames and mounts again.

Reproducing example

https://snack.expo.io/@terrysahaidak/reanimated-removes-view-bug

Video

https://drive.google.com/file/d/1HIfFt9vWX23lcnZukQSlw1n15-1ZWzcw/view?usp=drivesdk

Explanation

Why you ever want to change some values from animated one to regular?
The case I was playing with is lazy value initializations.
Since creating a lot of Animated.Value blocks the js thread (see #194) and I have a lot of them on the same screen, I do not initialize them until I start the animation by some event (for example, pressing the button or focusing the input).

鈿爄mportant 馃悶 Bug

Most helpful comment

I've investigated this issue and I think I have found the problem.

First of all - this is NOT an Android problem - this is happens on iOS as well (change title, @terrysahaidak ?), it just happens so quickly that it is hard to see it :)

The problem is evident if you attach a spy function to the message queue and is mostly visible for width/height/flex style properties (or style properties that forces a layout update).

The view is not recreated as we first assumed.

The issue is that when you update the styles the view will receive a call to update its properties through the UIManager updateView function - and the property value for the width/height/flex prop has a null value since the reanimated node has not yet been created. Creating the node happens _after_ the call to updateView, and from there on everything is good.

I'm investigating the possibilities for a fix and will come back :)

All 23 comments

@terrysahaidak Did you ever find the cause of this issue in the Android source code? I'm struggling with the same issue...

Hi @chrfalch, unfortunately not yet :( I believe @ddzirt has been able to found something.

Ok - is @ddzirt listening in here? Would be great to try to work together to find a solution.

@chrfalch Yes, I am investigating this when I am relatively free. From initial investigation - the issue is that view is recreated when animation starts.

Thanks for reaching out, @ddzirt :) Yes, that is also what I've seen. Switching style from a regular style to an animated style causes the view to be recreated.

This is very visible on Android, but it is also looks like this is can be seen on iOS in some circumstances. Any idea what causes this view recreation?

I went through node creation, and I think that Animated.Value is not used for the view before animation is actually triggered.

Regarding iOS I think there is the same issue, since I saw the same thing when I tried using new feature on iOS. But I saw that two days ago and had no time yet to confirm it in isolated project instead of work project

Have you been able to identify the area in the source code that causes this recreation?

Unfortunately not yet

New feature that gave me the same bug was Transition. I used the same code as in example

I asked @osdnk about this as well. Hopefully we can find some kind of solution to this. Feel free to shout out if you want me to investigate or test out something. I'm currently working on a big rewrite of Fluid Transitions that uses Reanimated and this is what stops me at the moment.

I am in a need as well, so I'll be investigating more and if I'll find something there will be more posts

Ok, please don't hesitate contacting me if you find something.

I've investigated this issue and I think I have found the problem.

First of all - this is NOT an Android problem - this is happens on iOS as well (change title, @terrysahaidak ?), it just happens so quickly that it is hard to see it :)

The problem is evident if you attach a spy function to the message queue and is mostly visible for width/height/flex style properties (or style properties that forces a layout update).

The view is not recreated as we first assumed.

The issue is that when you update the styles the view will receive a call to update its properties through the UIManager updateView function - and the property value for the width/height/flex prop has a null value since the reanimated node has not yet been created. Creating the node happens _after_ the call to updateView, and from there on everything is good.

I'm investigating the possibilities for a fix and will come back :)

@chrfalch nice, this at least gives us a proper understanding of what is happening

The problem seems to come from the createAnimatedComponent function's render method where animated nodes are removed from the style - which in turn causes React Native to emit a call to UIManager updateView with a null value for the width/height/flex value - ending up with a size equal to zero when recalculating sizes. The update from Reanimated arrives a little bit later, causing the view to flicker like it was removed for a frame or two.

Is this maybe something @osdnk or @kmagiera or someone else might have an idea on how to solve? Thanks in advance!

@chrfalch I think we could try to investigate how original Animated with useNativeDriver works. At least that is my initial plan on trying to fix this.

@ddzirt Another solution would be to take over all animatable properties and styles and not separate those that have animated nodes from those that doesn鈥檛. This way reanimated would have full control over updating props on views without the synchronization issue. I鈥檓 have made a simple test doing this and it seems to be working fine.

@chrfalch does it impact performance, by any chance? Especially if entire screen is fully covered by animations? I mean the loading/initialization time needed for reanimated to kick in. And if you have an example it would be great if you could share

I'm traveling today so I won't be able to share anything - will try to see if I can get something together in the next few days. Don't think it will affect performance, what I'm suggesting is only to change updating props to "all from reanimated" instead of "some from react native, some from reanimated".

No worries, I have free time only on weekends to play around the code, so I can wait.

About performance - it is just to be sure :)

I've added a simple solution where I remove style from props in the Animated component. All styles for a component are transferred through an AnimatedStyle node - not just the style containing animated values.

The patch works on iOS - haven't got the time to fix it for Android, but a similar fix as the one I've done to the REAStyleNode.m should be implemented in the StyleNode.java (and utils.processMapping).

The only drawback I have found is that the component will be drawn once by React Native without styles before Reanimated applies styles. This should be fixable.

Here is my patch:

diff --git a/node_modules/react-native-reanimated/ios/Nodes/REAStyleNode.m b/node_modules/react-native-reanimated/ios/Nodes/REAStyleNode.m
index fbd80a7..e23bee1 100644
--- a/node_modules/react-native-reanimated/ios/Nodes/REAStyleNode.m
+++ b/node_modules/react-native-reanimated/ios/Nodes/REAStyleNode.m
@@ -21,7 +21,12 @@ - (id)evaluate
   NSMutableDictionary *styles = [NSMutableDictionary new];
   for (NSString *prop in _styleConfig) {
     REANode *propNode = [self.nodesManager findNodeByID:_styleConfig[prop]];
-    styles[prop] = [propNode value];
+      if(propNode) {
+        styles[prop] = [propNode value];
+      }
+      else {
+        styles[prop] = _styleConfig[prop];
+      }
   }

   return styles;
diff --git a/node_modules/react-native-reanimated/src/core/AnimatedNode.js b/node_modules/react-native-reanimated/src/core/AnimatedNode.js
index 914edcd..faefa5d 100644
--- a/node_modules/react-native-reanimated/src/core/AnimatedNode.js
+++ b/node_modules/react-native-reanimated/src/core/AnimatedNode.js
@@ -38,11 +38,11 @@ function runPropUpdates() {
   loopID += 1;
 }

-let nodeCount = 0;
+let nodeCount = -100000;

 export default class AnimatedNode {
   constructor(nodeConfig, inputNodes) {
-    this.__nodeID = ++nodeCount;
+    this.__nodeID = --nodeCount;
     this.__nodeConfig = sanitizeConfig(nodeConfig);
     this.__initialized = false;
     this.__inputNodes =
diff --git a/node_modules/react-native-reanimated/src/core/AnimatedStyle.js b/node_modules/react-native-reanimated/src/core/AnimatedStyle.js
index fefc318..000acf7 100644
--- a/node_modules/react-native-reanimated/src/core/AnimatedStyle.js
+++ b/node_modules/react-native-reanimated/src/core/AnimatedStyle.js
@@ -11,6 +11,12 @@ function sanitizeStyle(inputStyle) {
     const value = inputStyle[key];
     if (value instanceof AnimatedNode) {
       style[key] = value.__nodeID;
+    } else {
+      if (key.toLowerCase().includes("color")) {
+        style[key] = processColor(inputStyle[key]);
+      } else {
+        style[key] = inputStyle[key];
+      }
     }
   }
   return style;

@@ -51,7 +57,7 @@ export default class AnimatedStyle extends AnimatedNode {
       const value = style[key];
       if (value instanceof AnimatedNode) {
         updatedStyle[key] = value.__getValue();
-      } else if (value && !Array.isArray(value) && typeof value === 'object') {
+      } else if (value && !Array.isArray(value) && typeof value === "object") {
         // Support animating nested values (for example: shadowOffset.height)
         updatedStyle[key] = this._walkStyleAndGetAnimatedValues(value);
       }
diff --git a/node_modules/react-native-reanimated/src/createAnimatedComponent.js b/node_modules/react-native-reanimated/src/createAnimatedComponent.js
index bca82bc..96dabf6 100644
--- a/node_modules/react-native-reanimated/src/createAnimatedComponent.js
+++ b/node_modules/react-native-reanimated/src/createAnimatedComponent.js
@@ -202,8 +202,8 @@ export default function createAnimatedComponent(Component) {
       const props = {};
       for (const key in inputProps) {
         const value = inputProps[key];
-        if (key === 'style') {
-          props[key] = this._filterNonAnimatedStyle(StyleSheet.flatten(value));
+        if (key === "style") {
+          props[key] = null; //this._filterNonAnimatedStyle(StyleSheet.flatten(value));
         } else if (!(value instanceof AnimatedNode)) {
           props[key] = value;
         }

Is there any updates on this? This is still an issue and makes impossible to have any components that have any animated initial state along side with regular styling.

Can you guys check if #1027 fixes that issue for you?

Was this page helpful?
0 / 5 - 0 ratings