The following are the recommended best practices for optimizing performance on You.i React Native-based apps:
When a bundle is loaded, there is the option to set the mode the application should run in.
It is recommended to run your application in release mode (dev=false
).
When running in development mode, there is additional work being done to provide good messaging to the developer, including validating propTypes and other assertions.
It is also recommended to profile performance of your application in release mode.
Ensure that the app is running with the minified=true
flag.
Logging out information to the console slows down the javascript thread and may cause a bottleneck depending on the frequency of the logs. It is recommended to not log information in release mode. Logging may also come from third-party libraries. You can remove them via a babel plugin.
The console.log
statements, especially the ones that do string concatenation, can be very expensive, especially on low-end devices.
Remove them for better performance.
Depending on how your app is written, you may see a significant performance improvement by making use of the @babel/plugin-transform-react-constant-elements
package.
You can do the following to improve the performance of lists in your app:
As the initialNumToRender prop sets the number of items to render on the first pass, therefore, set it to 1.5 x the number of items that can fit on the screen at once for faster load times.
The windowSize prop determines the maximum number of items rendered outside of the visible area, in units of visible lengths.
So if your list fills the screen, then windowSize={21}
will render the visible screen area plus up to 10 screens above and 10 below the viewport.
Reducing this number will reduce memory consumption and may improve performance, but will increase the chance that fast scrolling may reveal momentary blank areas of un-rendered content.
Setting it to 4 can provide serious performance and load time improvements.
However, it should be noted that you must also define the getItemLayout
prop so that layout will be calculated correctly.
To allow FlatList to correctly calculate layout, you need to know the total height and width of the list items outside the currently visible viewport, and pass them in using the getItemLayout prop.
The height specified in an AE composition will not be able to be mapped directly.
Below are some utility functions to convert composition height in AE to its height needed for getItemLayout
:
calcItemLayoutHeight(height) {
return height * (Dimensions.get('window').height / 1080);
}
For horizontal lists, you can use this to get the width:
calcItemLayoutWidth(width) {
return width * (Dimensions.get('window').width / 1920);
}
For the best results, ensure your lists have a uniform item size.
It is recommended to use keys for list items so that they will not be re-rendered when an item is added or removed from the list.
Otherwise the list will not know which items have changed, and will update everything.
Keys should be a string
explicitly.
Removing ImageSet timelines on image assets drastically increases performance of lists.
React Navigation is the de facto navigation framework for React Native apps. At the moment there are no performance improvements to be had from it, but some things to be aware of:
As a result, it is recommended to:
You can do the following to optimize the re-renders in your app:
Most custom React Components extend from Components instead of PureComponents.
The main difference between the two is the shouldComponentUpdate
method contents.
For a Component, each time the props or state changes, it will re-render, even if they are the same.
A PureComponent will do a shallow comparison between the old state, new state, old props and new props and determine if a re-render will occur.
It is recommended to use a PureComponent as the default to extend from.
Components should only be extended if the user intends to implement their own custom shouldComponentUpdate logic.
A re-render will occur each time a state variable is changed.
As a result, state variables should only contain graphical values, such as colors, border styles, text content, etc.
You may also consider extending from a Component and implementing the shouldComponentUpdate
method if you require fine control over when a re-render should occur, or extending from a PureComponent
otherwise.
A re-render will occur each time the prop values for a component is changed.
The component should only be passed the props that it needs.
You may also consider extending from a Component
and implementing the shouldComponentUpdate
method if you require fine control over when a re-render should occur, or extending from a PureComponent
otherwise.
A re-render will occur each time the prop values for a component is changed. If your application is making use of redux, it is recommended to have a child component access a prop in the store instead of passing that prop to the child. As a result, re-renders will be avoided.
An inline function is a function that is defined within the render method of a component and passed down to a child component as a prop.
For example, functions are referred to by pointers.
When an inline function is created, a new local instance of a pointer to that function is created.
As a result, the prop called onPress
will receive a new pointer each time the state is updated, which causes a re-render of the Button
component.
Therefore, it is recommended to avoid using inline functions, and instead provide references.
Why-did-you-update is a third-party tool that allows you to console log when unnecessary re-renders occur. You.i TV recommends that you install version 0.2.0, and only enable it for debug builds.
Break your app into small components to reduce re-renders.
Remember the following points for optimizing text in a You.i React Native based app:
To improve the performance of text rendering, you can pregenerate text data files. See Pregenerating Text Data Files.
Remember the following:
To optimize the size of your text Atlas, you need to override CYIApp::UserConfiguration()
, then make a CYIFrameworkConfiguration
instance, call SetTextAtlasSize
on it, and return it.
See the following example:
CYIFrameworkConfiguration MyApp::UserConfiguration()
{
CYIFrameworkConfiguration configuration;
configuration.SetTextAtlasSize(512);
return configuration;
}
Overdraw occurs when a screen pixel is drawn more than once. A single pixel overdrawn doesn’t have much impact, but an entire screen being overdrawn can severely hamper performance of your app. There are a few tools and techniques you can use to detect when overdraw is happening and reduce it.
As of You.i Platform 5.1.0, you will be able to view the current overdraw of an app using a Development Panel widget. When active, the render will appear as in the image below:
The rendered colors refer to different levels of overdraw:
When the Overdraw Widget is active, the overdraw rating (an average of overdraw over an entire screen) is visible in the top right corner of the heads up display or HUD. The HUD is the black display bar at the top and bottom right of a development panel display:
You.i TV recommends that you keep this value below 2.5 to avoid performance impacts. In the visual overdraw display, this means avoiding any red or purple areas. Small sections of red are fine, but large areas of red or purple would indicate excessive overdraw.
Be aware that displaying the overdraw visually can have a heavy impact on performance. This can be avoided by displaying the overdraw in the HUD only.
To display overdraw in the HUD only:
Note While Android natively supports displaying overdraw, the reported values are not accurate because You.i Platform does its rendering through an Open GL surface rather than through Android views.
Reducing overdraw can be as simple as removing unnecessary (non-visible) items from a scene. Other times, reducing overdraw is more difficult. In either case, make use of the Overdraw Dev Panel widget to view where overdraw can be reduced.
Generally, it is best to focus on full-screen (or near-full-screen) items. Small spots with high overdraw are fine if the overall overdraw remains low (2.5 and under). However, small compositions with high overdraw count can significantly impact performance if those small compositions are repeated. For example, a poster asset card with a high overdraw value could significantly contribute to the overall overdraw as posters are often shown in lists.
The following are several techniques you can use to help reduce overdraw in your apps.
The easiest way to reduce overdraw is to avoid full-screen solids.
These are often used to set a background color on a scene.
Instead, set a color on the root composition in After Effects, and then add that scene to the app with LayerType::Opaque
.
The color of an After Effects composition is set through the Composition Settings:
The scene is then loaded like this:
GetSceneManager()->AddScene("MainComp",
std::move(pSceneView), 0, CYISceneManager::LayerType::Opaque);
This should only be done for full-screen scenes, as loading scenes with LayerType::Opaque
prevents the scene from having transparency.
Where possible, image layers should be combined into a single image to reduce overdraw. Consider the following After Effects composition:
Here the designer wants to get an ‘old timey’ look, so a transparent frame is added as well as an adjustment layer to simulate sepia:
This works, but significantly increases the overdraw of the scene. The initial scene with the image had an overdraw of 0.0 (since each pixel was drawn once only). The new scene, however, has an overdraw of 3.0—here the effects are particularly expensive.
If the image is known to be static, overdraw can be reduced by combining every layer into a single image. This would typically be done in Photoshop or in a similar tool. This would bring the overdraw back to 0.0, and would look identical.
Another common case where it would be beneficial to combine layers would be for static images with gradients. Combining the image with the gradient means that each pixel is only drawn once rather than twice.
Note It is generally not worth combining images when those images only cover a small portion of the screen.
Image views often have ‘placeholder’ graphics that are displayed while assets are downloading or decoding. When the image that should be displayed is available, the image is often animated in. However, that animation often leaves the placeholder graphic around, even if it is no longer visible. In order to avoid affecting overdraw, that placeholder graphic should be hidden somehow.
Consider the following three image views:
The first image view is a ‘plain’ image view without animation. The second is an image view with a placeholder and an opacity animation to ‘show’ the assigned image. The third is an image view demonstrating the technique being described here. Before an image is assigned, all three image views have the same overdraw:
However, after assigning an image, the overdraw of the second increases as the ‘new’ image node is revealed, overlaying itself onto the placeholder:
Note that the overdraw of the third image view (after the animation has completed) does not change. The way to implement this is to make the ‘reveal’ animation one frame shorter than its marker, and place a pair of ‘opacity’ keyframes on the placeholder layer(s) to hide that layer when the ‘real’ image has been fully animated.
The animation timeline ends up looking something like this:
While this tip is about image views specifically, it also applies to any animation that reveals (or hide) other compositions/layers. To keep overdraw low, always hide compositions that are fully overlaid by other opaque compositions. Note that either opacity or the visibility track can be used to hide layers for purposes of reducing overdraw.
C++ applications generally use the screen transition manager to transition between scenes. As a scene is transitioned out, that scene is automatically unstaged. However, showing overlays generally does not use the scene transition manager. As a result, the scene ‘underneath’ the overlay does not get unstaged and is still rendered. When using full screen overlays, it can be beneficial to manually hide (or unstage) the ‘bottom’ scene once the overlay has finished animating in. Just remember to show/stage the scene when dismissing the overlay!
Sometimes, image assets are created unnecessarily large. Note that even if an image pixel is completely transparent, it still gets rendered and counts towards overdraw. Consider the following image:
That image should be cropped such that the edges of the image only have non-fully-transparent pixels:
This image is approximately five times smaller, and thus results in five times less pixels drawn on the screen. As a bonus, that image also takes up less memory.
Clipping is sometimes used to hide parts of compositions. However, on some platforms, clipped parts still get rendered (and discarded), and thus count towards overdraw.
Two things should be done to avoid incurring overdraw when clipping is used:
Buttons and asset cards often have borders. Sometimes those borders are always visible, and sometimes they’re only displayed when a given button or asset card is focused. Borders could be done by using a transparent image like this:
Instead of using such an image, a border can be constructed from four solids. The following image shows how this is done (with separate coloring for each ‘side’ to make the structure more obvious):
Again, this is only worth doing when the surface covered by the bordered items is significant.
The following topics explain how to reduce node counts in your App:
Creating and deleting nodes are expensive operations, and the larger the tree is, the more time it takes for all three trees to keep in sync with one another and update appropriately. As a result, designers should strive to reduce the number of layers in their AE designs, and developers should strive to reduce the number of JSX tags.
It is recommended to avoid layering many items on top of each other, for example, multiple full-screen backgrounds. On lower-end devices this would drop the performance drastically.
Conditional rendering is rendering only the components needed based on a set of conditions.
It is similar to making use of the display:none
attribute in CSS.
If a component does not need to be shown, and it is not shown very often, conditional rendering should be used rather than making use of the opacity or the visibility attribute where possible.
If conditional rendering is used, and the component does not need to be shown, the component will not be added to the react trees, and will therefore reduce the number of nodes.
For example, instead of setting a loading screen to be hidden (i.e., visible=false
), it can be conditionally rendered based on whether the content has loaded or not.
Note This performance improvement is only intended for nodes that do not need to be shown or hidden frequently. If your node frequently toggles between being shown or hidden state, it is recommended to use the visibility or opacity properties instead of creating or deleting nodes.
The following table explains the difference between opacity, visibility, and conditional rendering:
Property | Occupies Space | Consumes Click | Nodes Exist |
---|---|---|---|
CSS (opacity:0) You.i React Native (opacity=0) |
✓ | ✓ | ✓ |
CSS (visibility:hidden) You.i React Native (visible=false) |
✓ | ✗ | ✓ |
CSS (display:none) You.i React Native (conditional rendering) |
✗ | ✗ | ✗ |