Every React Native mobile application needs native codebase with an entry point, known as ReactRootView
. Depending on the platform the implementation differs. Nevertheless as a rule of thumb we can say that React Native just needs a native screen where JavaScript runtime can manipulate components and views.
It means that whenever we create a React Native application from scratch we instantiate it on a (very basic, bare) native codebase. However, we can always use a different, already existing codebase to run React Native. This is how we've built HybridApp - we've created a new ReactRootView
, and taken all JavaScript code to run it within the existing mobile application (OldDot).
It means that HybridApp is a regular native application, which has an additional screen that runs React Native. HybridApp let us combine New Expensify and our classic app into a single mobile app for a seemless migration
If you have access to the closed-source Mobile-Expensify
repository, you are eligible to build HybridApp. The main difference between NewDot and HybridApp is that the native code is located in a different place. The native code is located under ./Mobile-Expensify/Android
and ./Mobile-Expensify/iOS
.
It is vital to understand, that modifying any code in ./android
and ./ios
folders in the root of the project will not affect the HybridApp build at all. In this case, if you'd like to change native code, you have to go to ./Mobile-Expensify/Android
and ./Mobile-Expensify/iOS
first. This is especially important when you want to remove native cache or Pods
manually.
While working on the HybridApp we've prepared a set of scripts to make the work with HybridApp easier. In general, the scripts npm install
, npm run clean
, npm run android
, and npm run ios
work on the following condition:
If you have cloned HybridApp to Mobile-Expensify
submodule, the default behaviour of the scripts changes to build HybridApp instead of NewDot.
In that case, what are the differences in each script?
- Without access to HybridApp:
- install
node_modules
in./
- apply patches from
./patches
- install
- With a cloned Mobile-Expensify submodule:
- install
node_modules
in./
- apply patches from
./patches
- cd to
Mobile-Expensify
- install
node_modules
for OldDot - apply patches from
./Mobile-Expensify/patches
-> for more info refer to Patches
- install
- Without access to HybridApp:
- clean all caches for NewDot by running
npx react-native clean-project-auto
- remove the following (you can find full documentation here)
./node_modules
./ios/Pods
./ios/build
./android/build
- clean all caches for NewDot by running
- With a cloned Mobile-Expensify submodule:
- remove the following:
./node_modules
./Mobile-Expensify/node_modules
./Mobile-Expensify/Android/.cxx
./Mobile-Expensify/Android/.gradle
./Mobile-Expensify/Android/build
./Mobile-Expensify/iOS/Pods
./Mobile-Expensify/iOS/build
- remove the following:
The scripts will also remove some deeper hidden cache, but crucial thing to understand is that cache for HybridApp are mostly located in Mobile-Expensify
submodule, alongside the native code.
- Without access to HybridApp:
- build and install standalone NewDot
- With a cloned Mobile-Expensify submodule:
- build and install HybridApp
This is a helper script that lets you install Pods
without changing directories. Thanks to it, you can keep working in the root of the project (./
), and still be able to install iOS dependencies.
- Without access to HybridApp:
- install
Pods
in./ios
- install
- With a cloned Mobile-Expensify submodule:
- install
Pods
in./Mobile-Expensify/iOS
- install
The build should be very straightforward. Assuming you've cleared cache, and have just cloned the Mobile-Expensify
submodule, you should do the following:
npm i
npm run pod-install
(if you build iOS)npm run ios
ornpm run android
If you encounter any problems, please refer to the [[#Pro Tips & Troubleshooting]] section, or (if you don't find your answer there) remove the cache by executingnpm run clean
OldDot code in Expensify/App
repo is located in Mobile-Expensify
directory, which technically is a git submodule. Even though it seems like an advanced concept, in reality it is very straightforward: it basically sets a commit hash from which Mobile-Expensify
code should be pulled.
In practice a submodule is just a regular git repository, set to a specific commit. However, if you need to push changes to Mobile-Expensify
, you can do all the same things as on a regular repository like: changing branches, pulling and pushing to a remote repository etc.
If you change the Mobile-Expensify
code, it will be seen by Expensify/App
as a commit hash change that may be pushed to a remote repository. Currently we automatically update the Mobile-Expensify
commit hash, therefore you don't need to do it manually. In order to avoid confusion you can update your .git/config
by running the following command: git config submodule.Mobile-Expensify.ignore all
. This way you won't accidentally change the commit hash for the submodule in your PR.
IMPORTANT: Please execute the following commands form the root of the project!
git submodule update --init
- it initialises the submodule, and pulls the
Mobile-Expensify
code from the commit set inExpensify/App
. - IMPORTANT: If you already have the
Mobile-Expensify
code, you don't need to run the command with--init
flag.
- it initialises the submodule, and pulls the
git submodule update
- it pulls the
Mobile-Expensify
code from the commit set inExpensify/App
. - very similar to:
git checkout <COMMIT_HASH>
- it pulls the
git submodule update --remote
- it pulls the
Mobile-Expensify
code from the newestmain
on the remote repository. - very similar to:
git checkout main
- it pulls the
There is a set of patches, that gets applied only to HybridApp. They are not required by a standalone NewDot build, therefore they have been located in ./Mobile-Expensify/patches
. If you'd like to add a new HybridApp specific patch you can run:
npx patch-package <PACKAGE_NAME> --patch-dir Mobile-Expensify/patches
The patch-package
takes .patch
files, and applies the diff to node_modules
after executing npm install
. It means that if you'd like to build NewDot after HybridApp, it is best to remove node_modules
, and run npm run i-standalone
to make sure that HybridApp-specific patches won't be applied.
If you need to setup your local environment, you can create a .env
file in the root of the project (aka Expensify/App
). The variables will be included into the native build, which means that in case of HybridApp they will be also visible on the OldDot side. There is no need to have another .env
file in Mobile-Expensify
.
If there are some connection problems with your local backend, please make sure you've included ENVIRONMENT=development
in your .env
file.
There are multiple thing that may cause the build to fail. This is a simple checklist that may help you with debugging:
- Read the error carefully - sometimes it explains the problem very well
- Look for a similar error in the Troubleshooting section
- Pull the newest
main
- Check if you have a recent version of the
Mobile-Expensify
submodule- if not, execute
git submodule update
, and rerun the build
- if not, execute
- Clean the cache by executing
npm run clean
(with proper platform flags) If the problem still exists, please share the error in the #expensify-open-source channel
There are additional helper scripts, that will help you to run a standalone NewDot, even if you have access to Mobile-Expensify
, and the default behaviour of the scripts builds HybridApp.
In this case you can do the following:
rm -rf node_modules
npm i-standalone
npm run pod-install-standalone
(required only if you build iOS)npm run ios-standalone
ornpm run android-stanalone
Alternatively, you may notice in package.json
that all these scripts are based on STANDALONE_NEW_DOT
environment variable. If you feel confident you can do export STANDALONE_NEW_DOT=true
to change the default behaviour in the current process of the terminal, and use regular commands (npm i
, npm run android
, npm run ios
etc.) to build NewDot.
It's a valid question, especially because clean builds may take some time. On the way I've noticed that many developers tend to rebuild the app from scratch, even though in some cases it is unnecessary. In this case, when should you rebuild the app?
- Whenever any code located in
./Mobile-Expensify
has changed - this means that we need to recompile OldDot/native code - Whenever you've pulled the newest main - not always necessary, but usually we don't analise what code has just been merged by
git pull
- Whenever
package-lock.json
has changed - this may indicate that some packages with native code were bumped (however it's not always necessary, see [[#Should I rebuild HybridApp after bumping anode_module
?]]) - Whenever you've updated
.env
files This means that if you changed only React Native code, and didn't pull any changes, the rebuilt is probably not necessary. If something doesn't work, you can always restart the Metro bundler using the following commandnpm run start --reset-cache
The package-lock.json
file contains information about exact versions of node_modules
that will be installed on your machine. If you've bumped a dependency on your PR you can easily check to see if you would need to rebuild the app by going to the ./node_modules/<PACKAGE_NAME>
, and seeing if there are any Objective-C, Swift, Java, Kotlin or C++ files. Usually they are located in ios
or android
folders.
Executing npm run clean
clears cache for React Native, Android, and iOS. It means that the whole process may take a while, even though you try to build only one platform. In this case you case you can pass additional arguments to specify which cache should be cleared in order to save some time:
- React Native:
npm run clean -- --react-native
npm
,node_modules
,watchman
, Metro
- Android:
npm run clean -- --android
build
,.cxx
,.gradle
- iOS:
npm run clean -- --ios
Pods
,DerivedData
,build
,cocoapods
- YAPL:
npm run clean -- --npm
npm
,Mobile-Expensify/node_modules
(OldDot-specific)
This is an error that indicates, that patches from ./Mobile-Expensify/patches
were not applied. In this case:
- open a new terminal
- run
rm -rf node_modules && npm i
- run
npm run android
Android Studio for some reason sometimes is unable to find node
, even though its terminal has correct access to all environment variables. According to StackOverflow's threads the problem started appearing in 2021. In some cases linking node worked (sudo ln -s "$(which node)" /usr/local/bin/node
), but the easiest way is to open Android Studio directly from terminal:
- close Android Studio
- run
open -a /Applications/Android\ Studio.app
Actually you should be happy whenever you see this error - it means that the build failed, but the .apk
hasn't been installed on your device/emulator. In order to fix this, refer to: [[#How to find an .apk
, and install it on your device?]]
IMPORTANT: It's easily to confuse this error with a very similar one: Failed to install the app. Command failed with exit code 1: ./gradlew installDebug
. In fact, this one may have many possible reasons, and in order to debug it correctly, you should look for a Gradle Task that failed.
This error indicates that YAPL JS (OldDot's JavaScript code) hasn't been built properly. In order to fix that, do the following:
- cd to
Mobile-Expensify
- run
npm run grunt:build:shared
After a successful build, gradle creates an .apk
file, which you can install on your android devices/emulators. There is a chance that eg. the app failed to install after a successful build, or you want to test the app on another device. In this case you don't need to rebuild the app, because you can reuse the existing .apk
. These are the steps how to do it:
- run
adb devices
- you should have only one device listed- if there are no devices connected: open an emulator, or connect a physical device
- if there are multiple devices: close the devices you don't use
- install HybridApp
- if you've build debug:
adb install -r Mobile-Expensify/Android/build/outputs/apk/debug/Expensify-debug.apk
- if you've build release:
adb install -r Mobile-Expensify/Android/build/outputs/apk/release/Expensify-release.apk
- if you've build debug:
This way the debug app will get installed from the terminal. Alternatively you can open Finder and go to the directory where the .apk
is located. Then you can drag the file directly to the emulator screen.
You can do a similar thing for NewDot if you know the path to the generated .apk
. In this case you can reuse the adb install
command providing a different path.
If you'd like to list all available .apk
s to install them on your device you can run the following commands:
- HybridApp:
find Mobile-Expensify/Android/build/outputs -name "*.apk
- NewDot:
find android/app/build/outputs -name "*.apk
If you'd like to build HybridApp in release
configuration you need to adjust one thing in the code, and run the build manually from the terminal.
- open
./Mobile-Expensify/Android/build.gradle
file - change
signingConfig signingConfigs.release
line tosigningConfig signingConfigs.debug
- cd to
Mobile-Expensify/Android
- run
./gradlew installRelease
The easiest way to see native logs is to use Logcat. In order to find it go to: View
> Tool windows
> Logcat
It's an error that you may encounter while executing npm run pod-install
. Unfortunately it's very generic, and indicates an error in some React Native CLI script. In order to debug it, you have to get deeper into JS script files of the @react-native-community/cli
package. If you haven't changed anything in the Podfile
, or haven't bumped react-native
the best thing to do is to remove and reinstall node_modules
and make sure all patches were applied correctly.
This is a very common error that may appear during an iOS build, it's especially annoying when it appears after executing npm run ios
, because it doesn't give any additional information that may be useful for debugging. In order to see the real error you need to open XCode and rerun the build from there. When the build fails, pick Errors Only
in the main panel, and extend the error by pressing an icon with 4 parallel lines on the right hand side. On the very bottom you should see the real error.
Error: MsgHandlingError(message: "unable to initiate PIF transfer session (operation in progress?)")
This is pretty common in XCode, but it's easy to fix. Usually it means that you've executed npm run pod-install
with XCode being open, and the IDE didn't handle the Pods
changes well. To fix it:
- restart XCode
This one is pretty enigmatic, and usually appears after subsequent android, and ios debug builds. In this case:
- close Metro
- rerun the iOS build
This error may appear after execution of npm run pod-install
. In this case you shaould do the following:
- cd to
Mobile-Expensify/ios
- run
pod repo remove trunk
If you'd like to build HybridApp in release
configuration, the best way to do it is to:
- open XCode
- go to
Product
>Scheme
>Edit scheme
- in the opened window go to
Run
section, and chooseInfo
tab - pick
release
instead ofdebug
forBuild Configuration
Remember, that release
configuration may not work fine on physical devices, if you don't have proper permissions, and ProvisioningProfile. The simulator should build without problems.
One option is to run the build directly from XCode and you'll see the logs appearing in a section below. However, if you already have the app running, you don't have to rebuild it, the only thing you need to do is to attach the debugger to the correct process by going to Debug
> Attach to Process
> Expensify