Building a mobile app in Rust and React Native, Part 1: Project Setup

The first post in series ‘building a mobile app in Rust and React Native’.

In this series I will describe process of connecting mobile front-end written in React Native with business logic written in Rust.

As an introduction to this article, I suggest reading a great series from John Gallagher: Building an iOS App in Rust, Part 1: Getting Started with Rust

The app we are building is a mobile wallet for ethereum cryptocurrency. It is able create brainwallet accounts, scan qr-codes with unsigned transactions and sign them.

The main reason why I decided to use Rust in this project was that I already had all modules written in it and ready to use (or at least I thought so).

Required tools

  • node.js (tested on v7.4.0)
  • npm (tested on 5.2.0)
  • rustup (tested on rustup 1.0.0 (17b6d21 2016-12-15))
  • rustc (tested on 1.19.0 (0ade33941 2017–07–17))
  • cargo (tested on cargo 0.20.0 (a60d185c8 2017–07–13))
  • android_ndk (tested on r13b)
  • Xcode (only, for iOS, tested on Version 8.1 (8B62))
  • $NDK_HOME envarionment variable set to ndk home directory (eg. /usr/local/opt/android-ndk)
  • $JAVA_HOME envarionment variable set to java home directory (eg. /Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home)

Setting up cross compilation

Once all dependencies are installed, lets set them up. We will start with installing all necessary toolchains.

# ios
rustup target add i386-apple-ios
rustup target add x86_64-apple-ios
rustup target add armv7-apple-ios
rustup target add armv7s-apple-ios
rustup target add aarch64-apple-ios
# android
rustup target add aarch64-linux-android
rustup target add armv7-linux-androideabi
rustup target add i686-linux-android

Now, let’s create a new project.

react-native init our_project

Next, create rust subdirectory, enter it and run

cargo new our_project

After those steps, the project directory should look like this:

__test__
android
app.json
index.android.js
index.ios.js
ios
node_modules
package.json
rust
/our_project
/Cargo.toml
/src

Next, let’s setup makefile the project and place it inside our_project dir.

ARCHS_IOS = i386-apple-ios x86_64-apple-ios armv7-apple-ios armv7s-apple-ios aarch64-apple-ios
ARCHS_ANDROID = aarch64-linux-android armv7-linux-androideabi i686-linux-android
LIB=libsigner.a
all: ios android
ios: $(LIB)
android: $(ARCHS_ANDROID)
sh copy_android.sh
.PHONY: $(ARCHS_IOS)
$(ARCHS_IOS): %:
cargo build --target $@ --release
.PHONY: $(ARCHS_ANDROID)
$(ARCHS_ANDROID): %:
cargo build --target $@ --release
$(LIB): $(ARCHS_IOS)
lipo -create -output $@ $(foreach arch,$(ARCHS_IOS),$(wildcard target/$(arch)/release/$(LIB)))

copy_android.sh is a shell script that copies our statically compiled libraries to jniLibs directory.

#! /bin/bash
mkdir -p ../../android/app/src/main/jniLibs
mkdir -p ../../android/app/src/main/jniLibs/x86
mkdir -p ../../android/app/src/main/jniLibs/arm64-v8a
mkdir -p ../../android/app/src/main/jniLibs/armeabi-v7a
cp ./target/i686-linux-android/release/libsigner.so ../../android/app/src/main/jniLibs/x86/libsigner.so
cp ./target/aarch64-linux-android/release/libsigner.so ../../android/app/src/main/jniLibs/arm64-v8a/libsigner.so
cp ./target/armv7-linux-androideabi/release/libsigner.so ../../android/app/src/main/jniLibs/armeabi-v7a/libsigner.so

At this point we can already compile static libraries for android and iOS.

*disclosure

If your rust code links C libraries, the compilation will fail. To make it work, we need to set up custom linker. It can be done inside rust/.cargo/config file. You can use create-ndk-standalone.sh to generate it.

[target.aarch64-linux-android]
ar = "/Users/marek/projects/ethcore/native-signer/NDK/arm64/bin/aarch64-linux-android-ar"
linker = "/Users/marek/projects/ethcore/native-signer/NDK/arm64/bin/aarch64-linux-android-gcc"
[target.armv7-linux-androideabi]
ar = "/Users/marek/projects/ethcore/native-signer/NDK/arm/bin/arm-linux-androideabi-ar"
linker = "/Users/marek/projects/ethcore/native-signer/NDK/arm/bin/arm-linux-androideabi-gcc"
[target.i686-linux-android]
ar = "/Users/marek/projects/ethcore/native-signer/NDK/x86/bin/i686-linux-android-ar"
linker = "/Users/marek/projects/ethcore/native-signer/NDK/x86/bin/i686-linux-android-gcc"

At this point our project directory should look like like this:

__test__
android
app.json
index.android.js
index.ios.js
ios
node_modules
package.json
rust
/.cargo
/
config
/our_project
/
Cargo.toml
/copy_android.sh
/Makefile
/src

In next blog post I will cover making calls between Rust and React Native.

Project’s github repo.