React Native serves as a practical technological solution, allowing the development of iOS and Android applications with native rendering capabilities using React and JavaScript. We built the Hyperswitch SDK using React Native to ensure code reusability and cross platform compatibility. Let's revisit the basics and look at everything React Native has to offer!
There are two primary benefits of using React Native. Firstly, it enables web developers to write mobile applications that feel native using the most popular JavaScript UI library.
Secondly, a significant portion of code written in React Native can be shared between platforms, streamlining concurrent development for both iOS and Android. Thus reducing engineering overhead involved in making separate iOS and Android Applications.
Lets have a 10,000 ft view of how this thing works on different Operating systems.
React Native brings JavaScript code and Native code (Java/Kotlin for Android and Objective-C/Swift for iOS) together and make them work seamlessly like a seasoned orchestrator.
The native code executed directly on the device but JavaScript needs a virtual machine to be run on.
So, you must be aware how JS works on a browser, it needs a JavaScript Runtime Environment which in turn contains a JavaScript Engine.
And the JS engine is responsible for interpreting and executing JavaScript code in a runtime environment. Some major JS engines are V8 (used in Chrome), SpiderMonkey (used in Firefox), and JavaScriptCore (used in Safari).
The iOS and devices have a built-in JavaScript engine called JavaScriptCore written in C++, which will compile and execute our JS code.
Since Android devices lack a native JS engine, React Native addresses this by including the JavaScriptCore engine as part of its framework.
However in the latest versions of react native, Hermes is the default JS engine. Hermes brings to the table enhancements in startup time, reduced memory usage, and a smaller app size as compared to JavaScriptCore
So the JavaScript code runs essentially on JS engine only and by knowing this we have achieved another level of abstraction about how React Native works under the hood.
The native code for Android (Java/Kotlin) and iOS (Objective-C/Swift) are composed in different programming languages.
They lack a direct means to communicate to JS code. However, there's a clever workaround: they establish communication indirectly by utilizing a shared data format—JSON. This communication is facilitated by a group of programs known as the Bridge. It enables the exchange of information between the JavaScript and Native layers via JSON messages.
The idea is the same as in the case of web applications, where the frontend and backend layers do not need to know anything about each other but they still understand the information they exchange.
When we're putting together everything to make our app, the native code written in Java or Objective-C becomes binary files in Java and C++.
The code written in JavaScript gets bundled up using something called the Metro bundler. Metro does a job similar to the Webpack bundler for web application, but it's specially fine tuned for React Native.
During the runtime, the JS code will run on the JavaScript VM, and native code runs directly on the device. The Bridge will transfer serialized messages between the two eco-systems. The messages are then deserialized and dealt with.
A thread represents a sequence of instructions executed by the CPU. In simpler terms, it is a pathway of execution.
Essentially, there are three threads of primary importance in React Native carry that takes care of all necessary operations.
This is the primary native thread where the app runs. It handles user interactions, showing things on the device screen, and it's the same thread used in all fully native applications.
This thread kicks off with the JavaScript thread. Its job is to figure out where things should go on the screen and build a structure of layout instructions in the JavaScript thread. React Native uses a layout engine called Yoga, which turns flexbox-based layouts into something the device can understand. It also comes into play when an app needs info from the device, like if you're working on animations and need the Native driver to handle them.
This is where the app's main operations happen, such as running JavaScript and React code for the business logic.
When the JavaScript thread requires access to specific native modules, for instance Bluetooth, it must transmit a message to the native thread. The JavaScript thread will dispatch a serialized JSON message to the bridge, which will optimize and relay it to the native thread. Subsequently, the message will be decoded on the native thread, leading to the eventual execution of the necessary native code.
The bridge has some inherent issues and limitations.
1. Asynchronous: It worked like passing notes between two layers, and one had to wait for the other, even when it wasn't necessary.
Although asynchronous information exchange through the bridge is generally extremely fast, there are situations where it may prove insufficient, and opting for a synchronous approach would be more advantageous. Issues may arise in certain edge cases when relying solely on asynchronous communication.
2. Single-threaded: Think of it like doing one thing at a time on a single road. Everything in the JavaScript world had to happen on this one road.
3. Extra Work: When one layer needed to talk to the other, it had to package the information in a certain way (serialize), and the other layer had to unpack it (deserialize). This was like putting things in a box before sending them, and then opening the box to use them. Even though they chose a simple and easy-to-read format (JSON), it added a bit of work.
The New Architecture made things better by saying goodbye to the old Bridge and welcoming a new way for different parts of the app to talk to each other. They introduced something called the JavaScript Interface (JSI), which that lets JavaScript and C++ understand each other. It facilitates allows a JavaScript object to hold a reference to a C++ and vice-versa.
Now, if one type of code wants to ask the other to do something, it can just directly talk to it without waiting or doing extra work. This has a bunch of cool advantages:
1. No More Waiting: Some things that used to take time and wait around can now happen right away. Now it is possible to synchronously execute functions that should not have been asynchronous in the first place.
2. Less Extra Work: Before, they had to package and unpack the information each time they talked. Now, they can just share info without going through all that trouble i.e the New Architecture doesn't have to serialize/deserialize the data anymore
3. Sharing is Caring: With this new way, they can easily use the same code on different devices without too much hassle. It's like having a set of tools that works well everywhere. By introducing C++, now it is possible to abstract all the platform agnostic code, making it easier to share seamlessly across different platform.
4. Enforcing Type Safety: In order to make it possible for JS to properly invoke methods on C++ objects and vice-versa, a layer of code automatically generated has been added. The code is derived from a JavaScript specification that requires typing using either Flow or TypeScript.