A Minimal React Native App for Android

Generators that build seed applications for you from templates appear to be the norm among front-end ecosystems. A recent foray into app develop prompted me to check out React Native. Right from the start, the documentation recommends creating a project from a generator — and the project it creates is big. It then doesn’t go on to explain what each of the individual files are doing.

I’m not a fan of this approach. I decided to create a new project from scratch and try to get it working in an Android emulator. This post follows that journey.

This app will not be ready for production. It is intended to be the starting point from which you can add what you need, when you need it. The goal here is to get something running.

Creating a React Native application

A minimal React Native application is simple. The hard part was getting it running on Android, but we will get to that later. The React Native application itself is just four files:

  • App.json
  • app.js
  • index.js
  • package.json

Starting with package.json, since that manages the packages, include React and React Native, and with appropriate versions:

{
  "dependencies": {
    "react": "16.11",
    "react-native": "0.62"
  }
}

The two files above it are necessary just to get things working. index.js must register the application:

import { AppRegistry } from 'react-native';
import App from './App';
import { name as appName } from './app.json';

AppRegistry.registerComponent(appName, () => App);

app.json need only include the name of the application:

{
  "name": "MyMinimalApp"
}

The final file is the interesting one. This is where the layout logic lives.

import React from 'react';
import { Text, View } from 'react-native';

export default function App() {
  return (
    <View style={{
	flex: 1,
	alignItems: 'center',
	justifyContent: 'center'
    }}>
      <Text>Hello, world!</Text>
    </View>
  );
}

If you’re curious about the odd HTML-like syntax, that’s JSX. JSX simply provides syntactic sugar to covert those tags into Javascript functions. If you’re curious about the meanings of the tags themselves, check out the API docs.

Creating the Android component

In order to run this on Android, a valid Android project must be created in a sub-directory called android in the root of the project. A valid Android project is composed of numerous files.

First we have the core Java files that comprise of the application itself.

  • app/src/main/java/com/myminimalapp/MainActivity.java
  • app/src/main/java/com/myminimalapp/MainApplication.java

We also have the Android manifest file used by the build tools and the Android operating system, as well as the styles file used to declare the app theme in the manifest.

  • app/src/main/AndroidManifest.xml
  • app/src/main/res/values/styles.xml

And finally, we have the Gradle files used to build the project. I’ve only included the Unix-specific gradlew file here, but on Windows you would have a gradlew.bat file. These files serve as wrappers for Gradle so that it can be executed without installation on the local machine.

  • build.gradle
  • gradlew
  • settings.gradle
  • app/build.gradle

The first Java class, MainActivity, has little boilerplate needed.

package com.myminimalapp;

import com.facebook.react.ReactActivity;

public class MainActivity extends ReactActivity {
    @Override
    protected String getMainComponentName() {
	return "MyMinimalApp";
    }
}

The name of the main component of the application must be stated so that React Native knows which component it must render.

The second Java file, MainApplication, must implement the one method of ReactApplication.

package com.myminimalapp;

import android.app.Application;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;

import java.util.List;

public class MainApplication extends Application implements ReactApplication {
    @Override
    public ReactNativeHost getReactNativeHost() {
	return new ReactNativeHost(this) {
	    @Override
	    public boolean getUseDeveloperSupport() {
		return BuildConfig.DEBUG;
	    }

	    @Override
	    protected List<ReactPackage> getPackages() {
		return new PackageList(this).getPackages();
	    }

	    @Override
	    protected String getJSMainModuleName() {
		return "index";
	    }
	};
    }
}

ReactNativeHost has two abstract methods that must be implemented. The final method, getJSMainModuleName(), must be overridden so that the correct Javascript file is executed on startup.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.myminimalapp">
  <uses-permission android:name="android.permission.INTERNET" />
  <application
    android:name=".MainApplication"
    android:usesCleartextTraffic="true"
    android:theme="@style/AppTheme">
    <activity
      android:name=".MainActivity"
      android:windowSoftInputMode="adjustResize">
      <intent-filter>
	<action android:name="android.intent.action.MAIN" />
      </intent-filter>
    </activity>
  </application>
</manifest>

AndroidManifest.xml points to our MainActivity and MainApplication classes, sets the app theme, declares necessary permissions, and specifies the core behaviour on startup. android:usesCleartextTraffic is necessary starting from Android API level 28.

The app theme is declared in styles.xml.

<resources>
  <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"></style>
</resources>

With that, the final step is to flesh our the build system. gradlew is a file generated by invoking Gradle inside the project.

$ gradle wrapper

This will also generate a gradlew.bat executable for use on Windows. settings.gradle declares the nested app project inside the Android project.

include ':app'

The two build.gradle files are all that’s left.

buildscript {
    ext {
	buildToolsVersion = "28.0.3"
	minSdkVersion = 16
	compileSdkVersion = 28
	targetSdkVersion = 28
    }

    repositories {
	google()
	jcenter()
    }

    dependencies {
	classpath("com.android.tools.build:gradle:3.5.2")
    }
}

allprojects {
    repositories {
	mavenLocal()

	maven {
	    url("$rootDir/../node_modules/react-native/android")
	}

	maven {
	    url("$rootDir/../node_modules/jsc-android/dist")
	}

	google()
	jcenter()
    }
}

This is the root build.gradle file. It declares the Android API versions to use and necessary dependencies.

The one inside app is more complex.

apply plugin: "com.android.application"

apply from: "../../node_modules/react-native/react.gradle"

android {
    compileSdkVersion rootProject.ext.compileSdkVersion

    compileOptions {
	sourceCompatibility JavaVersion.VERSION_1_8
	targetCompatibility JavaVersion.VERSION_1_8
    }

    defaultConfig {
	applicationId "com.myminimalapp"
	minSdkVersion rootProject.ext.minSdkVersion
	targetSdkVersion rootProject.ext.targetSdkVersion
	versionCode 1
	versionName "1.0"
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "com.facebook.react:react-native:+"
    implementation "org.webkit:android-jsc:+"
}

apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)

Where we can, we import the values used in the root build.gradle file. Otherwise, this file is again about declaring dependencies.

Running the app

Before you can see this app running on an emulator, you’ll need to set up your local environment:

From the root project directory (not the Android project directory), start the Metro bundler.

$ npx react-native start

Then, start the Android component.

$ npx react-native run-android

You should then see the app load up in your emulator.

Where to go from here

Most of this was learnt by taking apart a sample React Native project and seeing what I could get away with removing. Now it’s time to add things back in.

Even if a snappy UI was added and the app did something useful, it wouldn’t be ready to go onto the Play Store. There are steps that must be taken to get an app production-ready, but there are numerous tutorials covering that process. Until we get to that point, we have a codebase where we know the purpose of every file in it. If we run into a problem down the road, we’re more likely to know how to fix it because we built the application from the ground up. With the foundation in place, the rest of the journey is about incremental iteration.