Building AR Experiences with React Native: Bridging the Gap with Kotlin
Tags: reactnative, android, arcore, kotlin
Published: 2023-12-10 18:56:04

## Introduction

Recently I got assigned an interesting project which required having AR capabilities in a _React Native_ app for cross platform purposes. The initial idea was integrating [ViroReact](https://github.com/ViroCommunity/viro), but this very quickly turned out to be a bad idea. Missing features for the 3D-Models (model animations, ...) and it's general incompatibility with the newest [React Native](https://reactnative.dev) Version (0.72) made it unsufficient for our use case. So the decision was made to implement the functionality via native modules. And so began my descent into dependency hell 💾🔥...

This is part one of a multi part article series about the integration of AR services via native modules into a _React Native_ app. The first half of this series will be about the integration into an Android app, using [ARCore](https://developers.google.com/ar/develop) and [SceneView](https://github.com/SceneView/sceneview-android). In this part, you will learn how to set up Kotlin to write native modules.

- Part Two: [Integrating ARCore](https://dev.to/9bytes/navigating-the-ar-landscape-in-react-native-with-kotlin-2caj)
- Part Three: [Setting up iOS with Objective-C](https://dev.to/9bytes/seamlessly-integrating-native-modules-in-ios-for-react-native-ar-apps-5alp)
- Part Four: [Integrating ARKit](https://dev.to/9bytes/seamlessly-integrating-native-modules-in-ios-for-react-native-ar-apps-5alp)

You can follow along or download the full code of part one [here](https://github.com/Gh05d/RN3DWorldExplorer/tree/part-1).

## Coding

### Prerequisites

I am using the _React Native_ CLI for this project on a real device (simulators don't work well with AR APIs). Make sure to have finished the [setup](https://reactnative.dev/docs/environment-setup) for new _React Native_ apps on Android. Also download [Android Studio](https://developer.android.com/studio). You will need it.

### Setting up a new React Native project

Open up your favorite terminal on the OS of your choice (I am on _Ubuntu 22.04.3 LTS_) and use the following command to setup a [new React Native project](https://reactnative.dev/docs/environment-setup):

`npx react-native init RN3DWorldExplorer`

Then add the folder to the workspace of your favorite editor and change into the directory via `cd RN3DWorldExplorer`.

### Kotlin

When writing native modules, you have the choice to use [Kotlin](https://kotlinlang.org/) or [Java](https://www.java.com). We will be using Kotlin for this tutorial.

> Kotlin is a modern but already mature programming language. It's concise, safe, interoperable with Java and other languages, and provides many ways to reuse code between multiple platforms for productive programming.

Unfortunately, it does not work out of the box (This may change in future [React Native versions](https://reactnative.dev/docs/native-modules-intro)). We have to do the following steps to make it work with _React Native_:

- Add `kotlinVersion = "1.8.0"` to the `buildscript { ext` section of your _android/build.gradle_ nad `classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")` to the `dependencies` section. It should look like this:
  ![Project wide build.gradle file](image.png)

- Add `apply plugin: 'kotlin-android'` to the top of your _android/app/build.gradle_
- Add `implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"` to the `dependencies`` section of the same file
- Sync the project with [Gradle](https://gradle.org/install/) via the elephant button in the upper right

Now try running the app via `npm run start` and type _a_ for Android. The app should build and run now.

### Bridging native modules to React Native

#### Creating a native module

Following the steps outlined from the [docs](https://reactnative.dev/docs/native-modules-android), we will be creating our AR module.

For this, open up Android Studio, navigate via the project pane (upper left) to the _app/java/com.rn3dworldexplorer_ folder, right click on it, select new -> Kotlin Class and name it ARModule.kt. Then add the following code to the file:
`
```kotlin
package com.rn3dworldexplorer

import android.content.Intent
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod

class ARModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
    override fun getName(): String {
        return "ARModule"
    }

    @ReactMethod
    fun showAR(promise: Promise) {
       promise.resolve("Hello ARWorld")
    }
}
```

- With the `getName()` function, we will be able to [access](https://reactnative.dev/docs/native-modules-android?android-language=kotlin#module-name) our module in our React Native code.
- The `showAR()` function takes a [Promise](https://reactnative.dev/docs/native-modules-android?android-language=kotlin#promises) as a parameter for now. The reason is that these methods can't return values directly to JavaScript due to the asynchronous nature of the bridge between native code and JavaScript. In part 2, we will integrate a custom activity to display a 3D-model instead.

#### Registering the native module

Now we need to [register the module](https://reactnative.dev/docs/native-modules-android?android-language=kotlin#register-the-module-android-specific) with React Native:

> In order to do so, you need to add your native module to a ReactPackage and register the ReactPackage with React Native. During initialization, React Native will loop over all packages, and for each ReactPackage, register each native module within.

Create a new Kotlin file called _ArPackage.kt_ and copy this code into it:

```kotlin
package com.rn3dworldexplorer

import android.view.View
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ReactShadowNode
import com.facebook.react.uimanager.ViewManager

class GLBPackage : ReactPackage {
    override fun createViewManagers(
        reactContext: ReactApplicationContext
    ): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()


    override fun createNativeModules(
        reactContext: ReactApplicationContext
    ): MutableList<NativeModule> = listOf(GLBModule(reactContext)).toMutableList()
}
```

This file imports the created native AR - module and instantiates it via the `createNativeModules` function and returns it as a list of _NativeModules_ to register.

Now we just must add the _ARPackage.kt_ to ReactNativeHost's `getPackages()` method. Open up _MainApplication.java_ and add this inside the method: `packages.add(new ARPackage());`

It should look like this:

![MainApplication.java file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qv3toy80jj15ahphloik.png)

This bridges our native module with the _JavaScript_ part of our application. Sync the project one last time.

#### Add the native module to the app

It is [best practice](https://reactnative.dev/docs/native-modules-android?android-language=kotlin#better-native-module-export) to create a file for every native module in your code. So in the root of your project, create a new folder _modules_ and inside it a file named _ARModule.tsx_. Copy this code inside it:

```JSX
import { NativeModules } from "react-native";
const { ARModule } = NativeModules;

interface ARModuleInterface {
  showAR(): Promise<string>;
}

export default ARModule as ARModuleInterface;

```

Doing it this way allows us to properly [type](https://www.typescriptlang.org/) our module and be able to import it anywhere simply via `import ARModule from "./modules/ARModule";`

#### Update App.tsx

Right now we have the boilerplate setup for new _React Native_ projects in our _App.tsx_. Update the file with this code instead:

```JSX
import React from "react";
import type { PropsWithChildren } from "react";
import {
  SafeAreaView,
  ScrollView,
  StatusBar,
  StyleSheet,
  Text,
  View,
  Button,
} from "react-native";
import ARModule from "./modules/ARModule";

type SectionProps = PropsWithChildren<{
  title: string,
  description: string,
}>;

const Section: React.FC<SectionProps> = ({ children, title, description }) => (
  <View style={styles.sectionContainer}>
    <Text style={[styles.sectionTitle]}>{title}</Text>
    <Text style={styles.sectionDescription}>{description}</Text>
    {children}
  </View>
);

const App: React.FC<{}> = () => (
  <SafeAreaView>
    <StatusBar barStyle="light-content" />
    <ScrollView contentInsetAdjustmentBehavior="automatic">
      <View>
        <Section title="AR" description="Render the native AR module">
          <Button
            title="Show AR"
            onPress={async () => {
              const res = await ARModule.showAR();
              console.log(res);
            }}
          />
        </Section>
      </View>
    </ScrollView>
  </SafeAreaView>
);

const styles = StyleSheet.create({
  sectionContainer: { marginTop: 32, paddingHorizontal: 24 },
  sectionTitle: { fontSize: 24, fontWeight: "600" },
  sectionDescription: {
    marginTop: 8,
    marginBottom: 16,
    fontSize: 18,
    fontWeight: "400",
  },
});

export default App;
```

Run the app now, press the "Show AR" button and you should see a `console.log` "Hello ARWorld" in your terminal.

## Conclusion

This concludes the first part of this series on integrating AR services into _React Native_. This part focused on setting up a Kotlin environment to write native modules, a crucial step in bridging the gap between the high-level _React Native_ framework and the low-level efficiency of native Android development. Looking ahead, the established groundwork here will allow us to dive into the world of AR development in _Android_. The next part of this series will explore the integration of _ARCore_ and _SceneView_.