React Native Bridge cover

When the time has come

I understand how you feel. As a React Native developer, you’ve dedicated years to improve your skills and mastering every concept in the mobile world. Chances are, you started with a foundation in web-based ReactJS. You were intrigued by the possibilities of React Native, so you immersed yourself in its documentation, familiarized yourself with components like ScrollView and FlatList, and felt the excitement of creating your initial animation using the Animated API. You were amazed by functionalities such as Push Notifications and filled with joy as you took your first photo using the Camera module.

But sooner or later, there comes a time when you have to create your first native library. It might seem difficult and intimidating initially. From that point onward, you’ll need to grasp the basics of native Android, iOS, have a slight understanding of Objective-C, Kotlin, and how the React Native bridge functions. It can feel mystifying, leading you to hesitate when it comes to the idea of crafting your own native module. You might think, ”But what about Expo? Can’t I use it to quickly prototype my native module?” Indeed, Expo is a fantastic piece of software for that purpose. However, what about projects that can’t transition to Expo? How about truly understanding the concept of crafting native modules without the convenience of pre-built tools and simplified APIs?

React Native Old Architecture

Info icon

In this post, I will focus on the iOS side of things. We’ll explore how things were in the past, how they are at present, and what we can expect going forward. We’ll kick things off with a quick overview of the history of React Native modules.

When React Native was first introduced, Facebook developed a method for communication between JavaScript and Objective-C. This approach is often referred to as Paper Architecture or the Old Bridge. In this approach, your native module needed to be registered using the RCT_EXPORT_MODULE macro. Then, you’d define the public methods using either of the two other macros: RCT_EXPORT_METHOD or RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD. These methods would then be callable from JavaScript.

NativeModuleExample.m
#import <NativeModuleExample.h>

@implementation NativeModuleExample

RCT_EXPORT_MODULE()

RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(sayHi) {
   // do something sync
}

RCT_EXPORT_METHOD(sayHiAsync:(RCTPromiseResolveBlock) resolve
                       reject:(RCTPromiseRejectBlock) reject)
   // do something async
)

@end

However, a limitation of this approach was that calling these methods required converting all the data into JSON, transmitting it via the bridge, and then converting it back to its original format in the native environment. The same process occurred when sending data from Objective-C to JavaScript. So, what exactly was the issue with this approach?

If you have a background in web development, you’re likely familiar with the fact that serialization and deserialization processes take time, unfortunately causing your calls through the bridge to become slow and asynchronous. This means you would need to use constructs like Callbacks, Promises, Event emitters or the async/await syntax.

Info icon

While Paper architecture is often described as asynchronous, it doesn’t imply the same kind of asynchronicity found in JavaScript programming. In this context, it signifies that actions like the JavaScript layer having to wait for the native layer, and vice versa, were common. Methods might have been synchronous, but they would block the thread. Even Facebook suggested replacing RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD in favour of RCT_EXPORT_METHOD as a preferable option.

While this might be suitable for many scenarios, if your app heavily relies on communication via the bridge, performance problems can emerge. This raises a concern: With such asynchronous overhead, how can React Native truly live up to its “Native” name? How can we draw comparisons between React Native and native iOS or Android applications? Oftentimes, you might sense that the app running on your phone is built using React Native due to these factors.

If the story were to end there, it would certainly paint a sad picture. However, Facebook introduced a brilliant solution by creating a new architecture for communication between the native and JavaScript domains. We can refer to this as the New Bridge or the Fabric Architecture. So, what’s different?

With this fresh approach, we’re able to utilize C++ as the communication layer. Numerous functions that used to be asynchronous can now operate synchronously without blocking the thread. This opens up the possibility of constructing APIs in an incredibly versatile manner. The new architecture introduces only a marginal increase in code execution time, measured in mere milliseconds. There is no time-consuming serialization and deserialization of JSON. Thanks to this revamped bridge, we gain the capability to synchronously call any synchronous API from both iOS and Android. As a result, React Native, with its emphasis on code shareability, truly begins to shine!

New (Overwhelming) Architecture

Now, you might be wondering: How did they achieve this? How did Facebook devise a solution that managed to bypass such intensive serialization tasks? There’s a single magical term that holds the key: HostObject. While we’ll dive deep into HostObjects in the near feature, I’ll provide a fundamental overview of them in Part 4 of this series.

Info icon

Host object is an advanced topic, but for now, think of them as C++ classes that are accessible from JavaScript

I know what you might be thinking - “I don’t want to learn C++! I consider myself more of a web developer than a native one.”. But don’t worry, learning C++ isn’t a necessity! While you can choose to delve into it, it’s not mandatory. Facebook has introduced a concept called Codegen, which takes care of generating C++ bindings for you. This way, you can concentrate on the actual implementation rather than the inner workings of the new bridge. We’ll explore Codegen in depth and create our first module in part 2 of this series.

However, let’s face it, life rarely unfolds that straightforwardly. You can bypass C++ directly, thanks to generated bindings, but there’s one more aspect to consider. If you’re serious about your library and want to support every app within the React Native ecosystem, both old and new architectures need attention. What does this mean?

In essence, you have the flexibility to enable or disable the new architecture on a per-project basis. Granted, there are apps that have adopted the new architecture and managed to replace libraries without Fabric support. Yet, the current state of the React Native ecosystem reveals that the majority of apps still adhere to the Paper architecture.

The new architecture made its debut with React Native 0.68, and Facebook is putting its best efforts into popularizing this novel approach. To this end, there’s even a dedicated working group that assists library creators in transitioning their modules to Fabric. Additionally, the official React Native documentation has mentioned that it will soon deprecate the Old Bridge.

“Native Module and Native Components are our stable technologies used by the legacy architecture. They will be deprecated in the future when the New Architecture will be stable.” ~Facebook

However, it’s important to recognize that moving from the old to the new architecture will take some time before it becomes universal. This transitional phase is similar to Apple’s decision to replace Intel chips with the more advanced M1 chips. That shift took approximately 2 years to complete. It’s my hope that both this series and your engagement with it will contribute a small but significant part to the larger effort of establishing the new architecture. With this momentum, it’s possible that around 1 year from now, we could potentially bid farewell to the Paper architecture altogether.

Boilerplate has gone

Even though React Native 0.68 introduced the new architecture, we initially needed to augment our native code with C++ functions for registering and interacting with Fabric. In the subsequent React Native releases, both Facebook and the community played a significant role in streamlining this process by completely removing these bindings from the visible code. You can even compare versions 0.68 to 0.72 using the React Native Upgrade Helper.

Facebook invested considerable time to ensure a seamless transition between the Paper and Fabric architectures. In the most recent React Native release, we’re given the ability to reuse legacy (Paper) components in the apps with new architecture enabled. This is a game changer as previously every native component had to be migrated to Fabric. Check the details about this approach here.

Summary

Let’s circle back to the concept of crafting your new native module. Is it challenging? I believe so. It means learning a lot of new things. You might make mistakes and occasionally feel alone when troubleshooting issues. But don’t worry, there are people who are working hard to help. Take a look at individuals like mrousavy, Oscar Franco or companies like Software Mansion, Callstack and even the Expo team. Their efforts provide us with a gateway to the realm of advanced bridging, enabling us to craft our custom native modules.

In the upcoming posts, we’ll delve deep into the New Architecture. I’ll guide you through understanding how Codegen works, explaining what TurboModules are, exploring JSI, clarifying how Fabric View operates, and demystifying the mysterious HostObject. We will also construct a module using a Sweet API (Expo Native Modules), giving you a sneak peek into how building your native modules can be simplified.

Please accept my invitation to this fascinating journey. See you in the next post 👋

PS.

Since this is my first post in the world of programming, it’s only fitting to refer to it as the “Hello World” post. I’ve chosen to dive right into the React Native Bridging, so let’s greet the world in the languages we’ll be discussing throughout this blog:

hello.ts
console.log("Hello World from Typescript")
hello.m
NSLog(@"Hello World from Objective-C!");
hello.cpp
std::cout << "Hello World from C++!" << std::endl;
hello.kt
println("Hello World from Kotlin!")
hello.swift
print("Hello World from Swift!")

Did you enjoy the post?

Subscribe to the React Native Crossroads newsletter

Subscribe now

Do you want to support me even more?

github-mona

Sponsor me on Github

kofi

Buy me a coffee

x-twitter

Share it on Twitter

Jacek Pudysz | Copyright ©2024 | All rights reserved

Built with Astro 🚀