Unlocking You.i Engine One Capabilities with React Native Counterparts

John Cassidy
John Cassidy

When building an application with React Native on You.i Engine One, there are situations where the SDK provides functionality that is beyond the current out-of-the-box capabilities of Facebook React Native—but they are only exposed at the C++ level. This article will describe a way to use You.i Engine One to enhance your React Native development experience by reaching into native code and accessing those expanded feature sets.

Native Modules

A Native Module is a communication bridge between JS to specific C++ code. For our purposes, we will focus on communication from JS to C++, which are classes and methods exposed to JS in a way that provides JS methods to call with parameters.

Further Reading: Basic example of creating a Native Module

Shadow Views & Counterparts

As discussed in an introduction to React Native and the After Effects Workflow, counterparts to JSX components are the UI Elements that are understood by You.i Engine One. They are created by Shadow Views that also represent the superficial JSX component (yoga node) in your React Native application code.

Shadow Views are responsible for receiving props from JSX components, and mapping them to a newly created You.i Engine One counterpart.

All ShadowViews are stored in a Shadow Registry that is owned by the React Native Platform Bridge. They are stored as a map from their node handle. When a JSX component is created, it is added to the Shadow Registry. When it is destroyed, it is removed.

Tapping into a Counterpart

With our knowledge of Shadow Views and how JSX components are linked to counterparts, we can summarize with a couple of statements:

All components that are represented in JSX have a counterpart view that is a You.i Engine One SDK UI Element.

All You.i Engine One SDK counterparts can be accessed by a Native Module to expose their native C++ headers and provide access to functionality that may be beyond the capability of a Facebook React Native comparable component.

To access a You.i Engine One SDK Element that is the counterpart to a JSX component, we first need to write a Native Module that will take a node handle. Shadow Views are stored in the Shadow Registry by this node handle, so having it allows us to reach into the Shadow Registry and access the appropriate Shadow View.

To find a node handle, we can use the functionality provided by Facebook React Native in JSX:

import { findNodeHandle } from 'react-native';

export class MySampleComponent extends React.Component {
 constructor(props) {
  super(props);
  this.myViewRef = React.createRef();
 }
 
 componentDidMount() {
  let nodeHandle = findNodeHandle(this.myViewRef.current)
 }
 
 render() {
  return <View ref={this.myViewRef} />
 }
}

Now that we know how to retrieve the handle of the component, we want to be able to provide that handle to a Native component. Using the above mentioned QuickStart Guide on Native Modules, let’s define one that accepts this node handle:

#ifndef _COUNTERPART_SAMPLE_MODULE_H_
#define _COUNTERPART_SAMPLE_MODULE_H_

#include <youireact/NativeModule.h>

class YI_RN_MODULE(CounterpartSampleModule)
{
  public:
 
  YI_RN_EXPORT_NAME(CounterpartSampleModule);
  YI_RN_EXPORT_METHOD(findCounterpart)(uint64_t tag);
};

#endif // _COUNTERPART_SAMPLE_MODULE_H_

Our next step is to find the Shadow component that maps to the node handle (tag) that we provide to the Native Module.

YI_RN_DEFINE_EXPORT_METHOD(CounterpartSampleModule, findCounterpart)(uint64_t tag)
{
    auto &shadowRegistry = GetBridge().GetShadowTree().GetShadowRegistry();
    auto pComponent = shadowRegistry.Get(tag);
    YI_ASSERT(pComponent, LOG_TAG, "Shadowview not found in ShadowRegistry.");
}

When we have access to the Shadow component, we can then access it’s counterpart view and then dynamically cast to the appropriate type for the Element in You.i Engine One. It’s important to note that for each step, we want to validate that we are working with the correct types and we do so by utilizing YI_ASSERT calls when appropriate (This will cause a SIGABRT exception with output explaining what went wrong).

YI_RN_DEFINE_EXPORT_METHOD(CounterpartSampleModule, findCounterpart)(uint64_t tag)
{
    auto &shadowRegistry = GetBridge().GetShadowTree().GetShadowRegistry();
    auto pComponent = shadowRegistry.Get(tag);
    YI_ASSERT(pComponent, LOG_TAG, "Shadowview not found in ShadowRegistry.");

    // using the shadow view, access it's counterpart view
    auto pCounterpart = pComponent->GetCounterpart();
    YI_ASSERT(pCounterpart, LOG_TAG, "Shadow view missing counterpart.");

    // dynamically cast the counterpart view to appropriate type
    CYISceneView *pSceneView = dynamic_cast<CYISceneView *>(pCounterpart);
    YI_ASSERT(pSceneView, LOG_TAG, "This is not a CYISceneView", tag);
}

With the above, we now have access to the counterpart view, and all the headers made available to it in the documentation (see Documentation for CYISceneView).

We can now call this NativeModule from JSX and provide it with the node handle tag.

import { findNodeHandle, NativeModules } from 'react-native';

export class MySampleComponent extends React.Component {
  constructor(props) {
    super(props);

    this.myViewRef = React.createRef();
  }

  componentDidMount() {
    let nodeHandle = findNodeHandle(this.myViewRef.current)
    NativeModules.CounterpartSampleModule.findCounterpart(nodeHandle);
  }

  render() {
    return <View ref={this.myViewRef} />
  }
}

Note: The above code will work in the exact same manner if we were to replace a <View> component with a <Composition> containing a <ViewRef> component. As discussed in an earlier introduction, the counterpart to a <ViewRef> is also a CYISceneView in the You.i Engine One SDK.

Example: Making a FlatList with Carousel Support

A common use case for a video streaming application is to provide a carousel on your lander screen, which is typically a list of featured content. Making a FlatList a carousel is (at the time of 0.58) not a supported feature for Facebook React Native. The solution is to search for third-party libraries, many of which rely on platform-specific native code making it not an ideal solution for a You.i Engine One React Native project that supports more than iOS and Android.

Carousel support is something that is built-in to You.i Engine One CYIListView. This makes it a prime use case for enhancing the functionality of Facebook React Native by tapping into the FlatList counterpart (a CYIListView) and accessing headers to directly enable carousel support:

import React from 'react';
import { FlatList, findNodeHandle, NativeModules } from 'react-native';

// The following class provides a wrapper class to a FlatList
// that will do whatever you ask a FlatList to do, but will 
// also take the reference, and use a Native Module to turn it's
// counterpart (a CYIListView) into a Carousel.

export default class Carousel extends React.Component {
  constructor(props) {
    super(props);
    
    this.listRef = React.createRef();
  }
  
  componentDidMount() {
    let nodeHandle = findNodeHandle(this.listRef.current);
    
    // pass the node handle the native module to manipulate the counterpart CYIListView
    NativeModules.ListUtility.setCarousel(nodeHandle);
  }

  return() {
    return (
      <FlatList
        ref={this.listRef}
        {...this.props}
      />
    );
  }
}
#include "ListUtilityModule.h"

#include <view/YiListView.h>
#include <youireact/IBridge.h>
#include <youireact/ReactBridge.h>
#include <youireact/ReactBridge_inl.h>
#include <youireact/ShadowTree.h>

YI_RN_DEFINE_EXPORT_METHOD(CounterpartSampleModule, setCarousel)(uint64_t tag)
{
    auto &shadowRegistry = GetBridge().GetShadowTree().GetShadowRegistry();
    auto pComponent = shadowRegistry.Get(tag);
    YI_ASSERT(pComponent, LOG_TAG, "Shadowview not found in ShadowRegistry.");

    // using the shadow view, access it's counterpart view
    auto pCounterpart = pComponent->GetCounterpart();
    YI_ASSERT(pCounterpart, LOG_TAG, "Shadow view missing counterpart.");

    // dynamically cast the counterpart view to appropriate type
    CYIListView *pListView = dynamic_cast<CYIListView *>(pCounterpart);
    YI_ASSERT(pListView, LOG_TAG, "This is not a CYIListVIew", tag);
  
    // because assertions are noops in release builds, protect against crashes
    if (pListView)
    {
        pListView->SetHorizontalCarouselRule(CYIScrollingView::CarouselRule::Always);
    }
}
#ifndef _LIST_UTILITY_MODULE_H_
#define _LIST_UTILITY_MODULE_H_

#include <youireact/NativeModule.h>

class YI_RN_MODULE(ListUtility)
{
  public:
 
  YI_RN_EXPORT_NAME(ListUtility);
  YI_RN_EXPORT_METHOD(setCarousel)(uint64_t tag);
};

#endif // _LIST_UTILITY_MODULE_H_

By implementing the Carousel component, and populating it with a dataset (in this example images from Unsplash) we can create a carousel that works on all platforms supported by You.i Engine One.

A FlatList with its counterpart view set to be a Carousel running on a macOS build