Moving Network Requests to Native Modules: A Guide
Improving Your App's Performance
Hello, fellows!
Writing native code is intimidating for some, however moving network requests to native modules seems to increase the performance of the app.
This idea was inspired by Ram Narasimhan's talk about performance in React Native at Chain React 2019. Also, the lack of examples and the need for the open source community motivated me to write this app and article.
Before we start, let's be honest: Who doesn't want to work more comfortably with native modules and have their app run faster? 😄 Certainly not me. So let’s do some magic! 🪄
You can download the app here.
Overview
🚫 What this article is not:
- It doesn't give a full step-by-step tutorial on how to make network requests in Swift or Java
- It doesn't show you the best way to make a network request in native
✅ What it is:
- It shows how to make network requests using native code and then connect them to React Native
- It gives a complete example for both iOS and Android
- It helps beginners work with native modules
In a nutshell, this app's network modules will run in native, and when JavaScript is ready, response will be sent to JavaScript, to be handled.
For the network request, Google Books API was used as it has a trusted certificate.
https://www.googleapis.com/books/v1/volumes?q=harry+potter
By using this URL, we can get to Harry Potter books ⚡🤓🪄, and this is how the app will look in the end:
💡 An interesting fact: While I was writing this app, I was traveling to Belgium, and when I ran the app, I noticed books were automatically downloaded in Dutch:
So, moving forward, the list and app style were done in React Native using code that was pretty similar to what the React Native docs gave for using a FlatList. Please make sure you'll check this link.
Network Modules 🧱
Native Modules are quite awesome. These modules are built in native and allow us to expose Java and Objective-C/C++ code to JavaScript, so that we can run any native code from within JavaScript.
Having network requests done in native will greatly speed up things. We'll talk about how to do that on each platform, and then we'll use the data we got in React Native.
For bridging, this cheat sheet was a big help. In this case, promises were used on both iOS and Android.
From the MDN docs:
The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
So, if everything works well, the operation is either solved with a value, which means it was finished successfully, or rejected, which means it failed. In our case, if the data containing the books is successfully retrieved, we can use them in our app.
iOS
As for iOS, I've created a native module called NetworkModule
, which is written in Swift.
// NetworkModule.swift
import Foundation
@objc(NetworkModule)
class NetworkModule: NSObject {
@objc
func getBooks(_ resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) -> Void {
//Here, a network request is made, which is either solved with a value or rejected
}
The @objc
modifiers ensure that the class and functions are properly exposed to the Objective-C runtime.
To make the module and getBooks()
function available to React Native, an Objective-C file was created for this:
// NetworkModule.m
#import "React/RCTBridgeModule.h"
@interface RCT_EXTERN_MODULE(NetworkModule, NSObject)
RCT_EXTERN_METHOD(getBooks: (RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
@end
Since we are mixing Swift and Objective C, we also need an extra bridging file to let Swift access the Objective-C files. But if you add a Swift file through the File>New File
menu, Xcode will make this header file for you. Here, you have to import RCTBridgeModule.h
:
#import <React/RCTBridgeModule.h>
Finally, in the getBooks
function, a simple request is made to make an HTTPS request using the URLSession
API.
You can find my code here, where I fetched data and then extracted the necessary information in native, in our case titles
, for displaying in the list.
However, you can easily find step-by-step guides for making simple requests in Swift, like this one.
Android 📱
Bridging on Android is a little different.
I’ve created the NetworkModule
written in Java inside android/app/src/main/java/com/networkbridge
folder. This class will extend the ReactContextBaseJavaModule
class. Module contains getName()
function that returns the name of the module and our function called getBooks()
that we want to export to React native:
// NetworkModule.java
@ReactMethod
public void getBooks(Promise promise) throws IOException {
//Here, a network request is made, which is either solve with a value or rejected
}
All native module methods that can be called from JavaScript must have the @ReactMethod
annotation.
In the same folder, we must create a new Java Class called MyAppPackage.java
. This class is needed to register the native module with React Native by adding it to ReactPackage
. More details you can find in the docs.
package com.networkbridge;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class MyAppPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new NetworkModule(reactContext));
return modules;
}
}
Besides that, you must search in your MainApplication.java
the method called getPackages()
and add MyAppPackage
to the list like this:
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new MyAppPackage());
return packages;
}
The request is pretty similar to iOS and the code can be found here using a helper method called getJSONObjectFromURL
with HttpURLConnection.
Now that iOS and Android have been set up, let's move on to React Native and figure out how to get these network responses.
React Native ⚛️
First, you must import NativeModules
in App.js
and then access the module:
import { NativeModules } from 'react-native';
const { NetworkModule } = NativeModules;
Since I’ve already mentioned that we’re using Promises, we will use await
inside the async function fetchBooks()
to call the module’s getBooks()
and wait for it to finish. The async/await
syntax is used to avoid having to explicitly set up promise chains.
Furthermore, this function is called inside a useEffect
hook, which will only run once because the second parameter is set to an empty array. The useState
hook is used to save the books, which can then be used in the FlatList
.
let [books, setBooks] = useState([]);
useEffect( () => {
async function fetchBooks() {
try {
const fetchedBooks = await NetworkModule.getBooks();
const mappedBooks = fetchedBooks.map((book) => ({id: uuidv4(), title: book }));
setBooks(mappedBooks);
} catch (e) {
console.error(e)
}
}
fetchBooks();
}, []);
The titles of the books that were fetched are then mapped to unique ids so that mappedBooks
looks like this:
const mappedBooks = [
{
id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
title: 'Harry Potter: Exploring Hogwarts',
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d72',
title: 'Harry Potter and Philosophy',
},
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28bc',
title: 'The Ultimate Harry Potter and Philosophy',
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f6d',
title: "Harry Potter, You're the Best!",
}
…
];
When the books' status has been updated, FlatList
will work this way:
<FlatList
data={books}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
And…Voilà! 🪄
We can now run the app on either Android or iOS and see the titles in the list. 🥳
Final Thoughts 💭
If you didn’t manage to download the code, you can find it here.
As I mentioned at the beginning, my primary goal was to show how to move network requests in native code on both iOS and Android, bridging them to react native, which might have a significant impact on the speed of an app. However, I hope this helped you become more comfortable with native modules as well.
Until next time, cheers! 😊 🥳 🥂