TV Apps — Handling Focus Styling In C++ For Performance Gains With React Native You.i
/ 4 min read
- Why
- The Current Scenario
- After Effects and Timelines
- Styling In JavaScript
- Styling in C++
- Conclusion
- Further
- Demo Code
- Learn more
Why
If you are building a TV app on very low-end devices like Fire TV stick and Roku sticks. And you want to animate or change the styling of a React Native item when receiving and losing focus, you might notice a lag if compared to user input. The following solution can fix this lag.
The Current Scenario
User input is handled first in C++, then the events for onFocus
and onBlur
are dispatched for the JavaScript callbacks to be handle them. This results in an inevitable lag. Thus, if you are applying a visual feedback in React when the item receives focus or blur, it will happen after the actual input was handled.
Initially, the difference between when the input is handled in C++ and the dispatch of the events to JavaScript is very little. However, since the JavaScript executor is running in a separate thread, it can be busy when the events are dispatched, thus causing the lag to increase.
After Effects and Timelines
Since responding to events in C++ will result in a faster response, You.I Engine has a few default timelines in some AE widgets/components which have their timelines handled in C++. For instance, when you make use of the After Effects CYIPushButtonView, it has FocusIn
and FocusOut
timelines which you do not have to connect to in JavaScript for them to be played, they are automatically handled by the Engine in C++ when the events occur.
There are some cases where using AE is not desired by the team. In these cases you can explore the following alternative to animate your button in response to Focus In and Focus out.
Styling In JavaScript
What many decide to do is have a local state for focus, and when the button’s onFocus
and onBlur
update that state to change the styling or trigger an animation. As was stated before this results in a lag.
Styling in C++
Instead, we can add Signal listeners to the CYISceneView’s GainedFocus
and LostFocus
.
The following button will have no onFocus
visual feedback.
class Button extends Component {
render() {
return (
<TouchableWithoutFeedback onPress={this.props.onPress}>
<View>
<Text>Title</Text>
<Text>Description will go here</Text>
</View>
</TouchableWithoutFeedback>
)
}
}
If we wanted to add visual feedback natively to it, we can write a Native Module achieve this.
ButtonFocusManager.h
ifndef ButtonFocusManager_h
#define ButtonFocusManager_h
#include <youireact/NativeModule.h>
#include <view/YiDecoratedView.h>
class YI_RN_MODULE(ButtonFocusManager)
{
public:
ButtonFocusManager();
YI_RN_EXPORT_NAME(ButtonFocusManager);
YI_RN_EXPORT_METHOD(setItem)(uint64_t tag);
};
#endif /* ButtonFocusManager_h */
ButttonFocusManager.cpp
#include "ButtonFocusManager.h"
#include <youireact/NativeModuleRegistry.h>
#include <scenetree/YiSceneManager.h>
#include <youireact/IBridge.h>
#include <youireact/ShadowTree.h>
using namespace folly;
using namespace std;
const CYIString TAG = "ButtonFocusManager";
YI_RN_INSTANTIATE_MODULE(ButtonFocusManager);
YI_RN_REGISTER_MODULE(ButtonFocusManager);
ButtonFocusManager::ButtonFocusManager(){};
YI_RN_DEFINE_EXPORT_METHOD(ButtonFocusManager, setItem)(uint64_t tag)
{
// ShadowRegistry contains all of the items available in the ShadowTree (similar to the virtual DOM).
auto &shadowRegistry = GetBridge().GetShadowTree().GetShadowRegistry();
auto pComponent = shadowRegistry.Get(tag);
YI_ASSERT(pComponent, TAG, "Shadow view with tag %" PRIu64 " not found in ShadowRegistry.", tag);
// For every React Native component we have a corresponding Widget (counterpart) in the Engine.
auto pCounterpart = pComponent->GetCounterpart();
YI_ASSERT(pCounterpart, TAG, "Shadow view with tag %" PRIu64 " doesn't have a counterpart.", tag);
// Almost all counterparts extend CYISceneNode, CYISceneView, and CYIDecoratedView, for styling get the CYIDecoratedView. (You can get more types like CYIPushButtonView if your item corresponds to one)
CYIDecoratedView * pSceneView = dynamic_cast<CYIDecoratedView *>(pCounterpart);
YI_ASSERT(pSceneView, TAG, "Error -- Could not cast tag to CYISceneView, tag: %" PRIu64 "", tag);
// The available Signal events to connect can be found in the documentation: https://developer.youi.tv/API_Docs/5.15/core/html/classCYISceneView.html#a930ea87ae241ad890fc04234f5ae2d64
pSceneView->GainedFocus.Connect([pSceneView](){
// You can set any styles here. See the documentation of CYIDecoratedView.
pSceneView->SetBorderThickness(10.0f);
pSceneView->SetBorderColor(CYIColor::FromString("red"));
});
pSceneView->LostFocus.Connect([pSceneView](){
pSceneView->SetBorderColor(CYIColor::Named().Transparent);
});
}
Result:
Conclusion
The advantage of You.i Engine over pure React Native solution is the shared codebase not only in JavaScript but also C++ — this is by default without any extra configuration. The C++ layer allows you to achieve native performance in your cross-platform code. This advantage contrasts to vanilla React Native since React Native does not offer a single codebase in the native layer, it requires implementation in for each platform’s language.
Further
The You.i Engine’s API surface in C++ has many capabilities which React Native does not, therefore, you can follow the same approach presented here to extend your components functionality when you need so. See the API Documentation to explore the possibilities: https://developer.youi.tv/API_Docs/5.15/core/html/index.html.
Demo Code
Learn More
You.i is a closed source SDK that enables you to build to 11+ platforms with a single codebase either in React Native or C++. To learn more visit them here.