Disclaimer:
If you find any inaccuracy in the text below, feel free to promote your finding so I can fix it
Hi, my name is Anton. I'm a full-stack software engineer. At the moment working on the React Native app for the scooter building company.
In this post, I will share my experience of adding additional native code functionality inside my app. I spent around a month trying to figure out different things around the process. This is what I learned:
Expo SDK and my setup
I use Expo SDK 45. When I write down these lines, Expo SDK 46 has already been released, but I haven't updated it yet.
What is Expo? It's a managed platform, which simplifies the process of development react native app.
Speaking about the ways to develop the app in react native ecosystem, we have two possibilities:
-
bare workflow. Usual react native project with two folders
android
andios
with the native platform configuration there (likeAndroidManifest.xml
andInfo.plist
). -
managed workflow. Expo manages these
android
andios
folders for us. It's quite convenient.
Everything looks bright when you live inside the Expo ecosystem and don't need some tricky native platforms API functions, which aren't officially supported by the Expo.
BUT if you are faced with the need to use some native unsupported stuff - don't despair, may you be comforted by the thought that you are not alone.
Here is my story...
Business task
Let me start from the domain field and business requirements:
Make the world a better place
Make the phone work as a BLE key for the scooter (BLE = Bluetooth Low Energy = Bluetooth 4.0).
BLE allows the device to work in two modes:
- central mode (like the client, which fetches information from other devices)
- peripheral mode (like the server, which provides some information endpoints)
I needed the phone to work in peripheral mode and advertise it with a custom name like "My supercool iPhone" (to tell the nearby devices that it exists).
I'd searched through google and GitHub for possible 3rd-party modules to work with this, but found nothing.
The most stable native module react-native-ble-plx allows working only in central mode.
Other solutions were either unstable, without the support of one of the platforms (Android/iOS), or without the feature to set a custom name when you advertise.
Long story short, I ended up with the need to write my native module to wrap up Kotlin and Swift code for work with this Bluetooth stuff.
Native module development
I wouldn't spend much time talking about the native module development itself, but here are some useful links:
- official docs
- project structure generator which I used, during the initial configuration, allows you to choose languages for the native platforms (like Kotlin and Swift).
Platform and Code
When working with Expo or React Native we can split the whole system on:
- Platform - underneath layer with all required integrations to the native platforms + JS engine + Bridge
- Code - our js code, which we usually write
When we add some additional native functions to our Platform we need to create our own client instead of Expo Go
we normally use. This client with all additional features inside is called Dev Client
(in Expo terminology).
We need to build dev clients for both Android and iOS.
Dev clients are built using EAS
tool. They can be built locally or remotely (on expo team cloud infrastructure). The problem with the last is that you have to wait in a queue on the free tier (up to 3 hours).
To run builds locally, I use the following commands:
"scripts": {
"start": "expo start",
"start-dev": "expo start --dev-client",
"build-dev-client-android": "eas build -p android --profile=development --local",
"build-dev-client-ios": "eas build -p ios --profile=development --local",
"release-android": "eas build -p android --profile release --local",
"release-ios": "eas build --platform ios --profile release --local"
},
in case you may need it, my eas.json
is here:
{
"build": {
"base": {
"env": {}
},
"release": {
"android": {}
},
"development": {
"extends": "base",
"developmentClient": true,
"distribution": "internal",
"releaseChannel": "default",
"android": {
"buildType": "apk"
}
}
},
"submit": {
"release": {}
},
"cli": {
"version": ">= 0.44.1",
"requireCommit": true
}
}
if you want local builds to be saved to some other folder (by default, they are saved in the project folder) - create EAS_LOCAL_BUILD_ARTIFACTS_DIR environment variable with the desired path.
On my old machine (MacBook Pro late 2015), a local build can take around 20-25 minutes for one package. (yet it's faster than waiting 3 hours in a cloud build queue :))
NOTE. In defense of my old MacBook buddy, I should say that on Expo servers the build of my react native project takes a similar amount of time.
Platform configuration (dev-client)
During the process of dev-client creating, when we use our ready native module and add it as a dependency to our expo app, it should automatically link all native stuff using react native auto-linking feature.
The only problem we may encounter here is related to permissions. In my case, I need additional Bluetooth permissions to set up.
Here we need to intercept in the expo process of building the apps/dev-clients (how it builds android
and ios
projects). We doing it using config-plugins. We can add some required modifications to the AndroidManifest.xml
and Info.plist
on this step.
I was lucky because there already was one written config that added Bluetooth permissions to native projects config-plugins/react-native-ble-plx. I simply used it.
We can test if expo modifies native folders as we suppose it should by running the command expo prebuild
and discovering the results (be ready to discard these changes from the repo if you use git and don't want to commit them).
expo prebuild
is the command which actually runs first by EAS
(expo build tool) when you try to build *.apk/*.aab or *.ipa packages.
NOTE. When your config plugin is ready don't forget to add it to "plugins" section of app.json
file.
Dev-client artifacts
So, at this point, you should have:
-
dev-client.apk
or Android. Can be installed on the real device by runningadb install dev-client.apk
-
dev-client.ipa
for iOS. I found a way to install it on the real iPhone 11 by downloadingApple Configurator
app from Mac App Store. There you can simply drag and drop the package on your phone and it will be installed.
When you have packages installed on your real device, you can run your JS code server by expo start --dev-client
(don't mess it up with the expo start
command, without the flag it will start the Expo Go
app instead of our dev client)
Summary
If you want to write your native module for any native feature and use it with Expo, you have to follow these steps:
1) use react-native-builder-bob
to generate a native library and test it with the bare react native app ('bare' means 'no managed workflow', just pure react native with iOS and android folders in the project)
2) if you want to work inside the Expo environment further, you need to build a 'platform' for your react native JS code, which will include your native module besides the basic expo supporting functions. This 'platform' is called 'dev-client' in terms of the expo.
3) if your native module needs some permissions, you need to write your own expo-config to modify the platforms permission files, you can take some existing ones as a basis
4) Then you need to install this platform/dev-client on your real device and start expo with the special --dev-client
flag.
5) In this way, you may continue to get all the benefits that Expo offers.
I admit that it can be tricky when you go through this technology and dependencies jungle for the first time.
NOTE. Recently, I found out that the Expo ecosystem has its way to work with native modules called Module API. I haven't tested this workflow yet. Probably, it will simplify some steps. I think this could serve as material for another article ;)
That's all for now. Thanks for your time. I hope you enjoyed the reading.
Top comments (0)