Logo
react native · node

Custom Native Modules with React Native & Expo - Part Two

July 27th, 2022
imgimgimgimg
Custom Native Modules with React Native & Expo - Part Two

In Part 1 of this two-part series on Expo config plugins, we set up a config plugin and configured it to work on iOS.

In this second part we will be writing a config plugin for Android, including the addition of native Java code and the resultant integration with the React Native project.

Continuing Our Calendar Module

To recap where we left off in Part 1, we were creating the simple Calendar module described in the React Native docs. Where our example differs is that we need to make it work on an Expo project in the managed workflow.

Let's resume and start looking at the Android side.

Configuring the Android Calendar Module

The first part of the Android setup will be very similar to what we saw previously on iOS. Again, the implementation of the native code itself is not our main concern, so we'll simply be taking the Java code from the React Native documentation.

First, let's get the new native files created:

mkdir -p ./plugins/native/android touch ./plugins/native/android/CalendarPackage.java touch ./plugins/native/android/CalendarModule.java

The CalendarModule will contain the specific functionality and the CalendarPackage bundles that into a package which can be added to the list of packages registered as native modules in the React Native project.

// CalendarModule.java package com.your-project-name; // Replace with real name import android.util.Log; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.Callback; @Override public String getName() { return "CalendarModule"; } @ReactMethod public void createCalendarEvent(String name, String location, Callback callback) { Log.d("CalendarModule", "Create event called with name: " + name + " and location: " + location); callback.invoke("random-event-id-123"); } // CalendarPackage.java package com.your-project-name; // Replace with your project's real name 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 CalendarModule(reactContext)); return modules; } }

Now that we've added the Java files, we can revisit with-android-calendar.js, where we'll be adding the logic to connect our new Android native module to the React Native project.

// with-android-calendar.js /* eslint-disable no-param-reassign */ const { withMainApplication } = require("@expo/config-plugins"); const fs = require("fs"); /** Relative to root of our project */ const ANDROID_NATIVE_CODE_PATH = "./plugins/native/android"; const ANDROID_FILES = ["CalendarModule.java", "CalendarPackage.java"]; /** Custom config plugin adding a native Android calendar module */ const withAndroidCalendar = (config) => { config = withMainApplication(config, (config) => { const { modResults } = config; const { path, contents: mainApplicationCode } = modResults; const addNativeModuleAt = path.replace(/\/MainApplication.java/, ""); /** Physically copy Android .java files to android folder. */ ANDROID_FILES.forEach((fileName) => { fs.copyFileSync( `${ANDROID_NATIVE_CODE_PATH}/${fileName}`, `${addNativeModuleAt}/${fileName}` ); }); config.modResults.contents = registerCalendarPackage(mainApplicationCode); return config; }); return config; }; /** Register CalendarPackage with Android package list */ const registerCalendarPackage = (mainApplicationCodeAsString) => { const codeToAddCalendarPackage = `packages.add(new CalendarPackage());`; if (!mainApplicationCodeAsString.includes(codeToAddCalendarPackage)) { mainApplicationCodeAsString = mainApplicationCodeAsString.replace( /return packages;/, ` ${codeToAddCalendarPackage} return packages; ` ); } return mainApplicationCodeAsString; }; module.exports = withAndroidCalendar;

Again, the Android implementation mirrors the iOS to a degree. As before, we physically copy the native Java files into the directory where React Native needs them.

Where the Android side differs is that we also need to add the Calendar package to the list of Java packages using the registerCalendarPackage method which accesses the MainApplication.java source code using the withMainApplication config plugin which ships with the @expo/config-plugins module.

Once again, we can run expo prebuild to verify that everything is working as expected.

With the iOS and Android implementations all finished, we can now set about writing some React Native code and test out the calendar module.

Expose the Native Module to React

Let's recap where we've got to.

We added the iOS and Android native code and rewrote the platform-specific config plugins to make sure the Objective-C and Java files were properly accessible to our React Native project.

What we now need is for React Native to be able to load in our native module and use it on the JavaScript side. First, let's create a new file called Calendar.js.

touch Calendar.js

Now let's configure this file to load the calendar module and export it:

// Calendar.js import { NativeModules } from "react-native"; const { CalendarModule } = NativeModules; export default CalendarModule;

We are now easily able to access the calendar module, so let's test it out by adding a basic button to trigger its createCalendarEvent method.

Using the Calender Module

Let's now modify App.js to add a simple button which, when pressed, invokes the createCalendarEvent method and displays the eventId in a simple alert.

// App.js import { StatusBar } from 'expo-status-bar'; import { StyleSheet, Alert, Text, TouchableOpacity, View } from 'react-native'; import Calendar from './Calendar'; export default function App() { return ( <View style={styles.container}> <Text>Tap the button to test the calendar module</Text> <TouchableOpacity style={styles.btn} onPress={() => { Calendar.createCalendarEvent("Birthday Party", "My House", (err, eventId) => { if (!err) { Alert.alert(`Calendar.createCalendarEvent called and returned event ID of ${eventId}`); } }) }}> <Text style={styles.btnText}>Test Calendar</Text> </TouchableOpacity> <StatusBar style="auto" /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center', }, btn: { marginTop: 24, padding: 12, borderRadius: 8, backgroundColor: '#000', }, btnText: { fontSize: 16, lineHeight: 20, fontWeight: '600', color: '#fff' } });

We can now run the project on an iOS simulator

expo run:ios

Cnce the project is running on the simulator, clicking the button should trigger a native method invocation and display the alert.

Splash Screen
Home Screen
Alert Message
Simulator Screenshots: Trying out the calendar native module

Summary

In this article we have looked at how custom Expo config plugins can let you add your own custom native code in the Expo managed workflow.

By using an example straight from the React Native docs and linking the module to the Expo project, we have seen how far Expo has come in terms of mimicking the functionality available in projects created through the React Native CLI.

For use in production with a more complex module, it would probably be better to create a separate repository and load that into your project. Indeed, this seems to be the approach recommended by Expo, but hopefully this article has given some good pointers on how to get started with writing your own config plugins.

Thanks for reading!

Share this itemimgimgimgimg

Related Articles

Native Modules with Expo Config Plugins: Part 1

Expo is a popular toolchain for React Native which simplifies the…

May 11th, 2022

Querying Firebase Analytics Data

When getting started with Firebase Analytics, it can be confusing to figure…

March 23rd, 2022

Comparing Next.js and Gatsby's Handling of Data

Next.js and Gatsby are modern frontend frameworks based on React. Typically…

February 8th, 2022

Logo
James Does Digital
Software Development
Cloud Computing
Current Address
Edinburgh
Scotland
UK
This site was created using the Jamstack.
All articles © James Does Digital 2024. All rights reserved.