Setup Kotlin for React Native Expo plugin

The Context

Working on a React Native Expo project which uses the EAS build tools for CI whilst also using a custom native module for Mapbox turn-by-turn navigation which is not supported by the Expo Go app out of the box. The feature required us to launch a native map activity and start a trip which starts navigation.
We only had a week to implement this feature.
notion image
The setup we had, already launched an activity in Java which opened the map activity.

The Problem

All the documentation for the Mapbox SDK was in Kotlin and there was only one example that I could find to follow which was quite extensive (it also uses data bindings, which are not recommended for Android in favour of Compose).
I had two options to trigger turn-by-turn navigation:
  1. Figure out the implementation in Java with no documentation of examples (through reading the source code from the SDK).
  1. Setup the Expo plugin to work with Kotlin and write the implementation in plugin.
I decided to explore option two.
I used Android Studio to convert my Java activity class into a Kotlin file so that my directoy structure looked like so:
|_MapboxNavigation.java |_MapboxNavigationViewActivity.kt
In my MapboxNavigation.java file, I was launching the activity with an intent:
ReactApplicationContext context = getReactApplicationContext(); Intent intent = new Intent(context, NavigationViewActivity.class);
My understanding was that Kotlin comes with out-of-the box interoperability with Java (i.e. I can import it as if it were a Java class), however when building the project, I was getting this error:
error: cannot find symbol: Intent intent = new Intent(context, NavigationViewActivity.class); ^

The Solution

It turns out that the gradle that comes with expo prebuild doesn’t support compiling Kotlin out-of-the box, and you will need to do some basic setup to get the project up and running!
Our debugging steps were:
  1. Remove the intent line of code that was causing the compilation issues and add a syntax error into the Kotlin file - if it is being compiled during our build step we will see an compilation error. This way we can check if Kotlin is being compiled at all.
  1. After running a build and seeing no comilation errors, we deduce that Kotlin is not setup correctly in the project.
  1. Leaving the syntax error in the Kotlin file, we iteratively update the app/build.gradle file and rebuild until we start to get compilation errors, which shows that Kotlin setup is working.
  1. We remove the sytax error and add back in the line that was initially causing an issue and see if we still get a cannot find symbol error.
Thanks to Nicolas @ BAM for helping me solve my problem!

The Code

We prepended our app/build.gradle file with the following and Kotlin files were then included in the build:
apply plugin: "com.android.application" apply plugin: 'kotlin-android' allprojects { repositories { mavenCentral() google() } } buildscript { ext.kotlin_version = '1.7.0' repositories { mavenCentral() google() } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } }
Here are the matching Java and Kotlin files inside android/app/src/main/java/com/example:
// MapboxNavigation.java package com.example; import android.content.Intent; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; class RNMapboxNavigation extends ReactContextBaseJavaModule { ReactApplicationContext context = getReactApplicationContext(); RNMapboxNavigation(ReactApplicationContext reactContext) { super(reactContext); } @Override public String getName() { return "MapboxNavigation"; } @ReactMethod void startNavigation() { ReactApplicationContext context = getReactApplicationContext(); Intent intent = new Intent(context, NavigationViewActivity.class); context.startActivity(intent); } }
// MapboxNavigationViewActivity.kt package com.example; import android.Manifest import android.annotation.SuppressLint import android.content.pm.PackageManager import android.os.Bundle import android.util.Log import android.view.View import androidx.annotation.CallSuper import androidx.core.app.ActivityCompat import com.facebook.react.ReactActivity import java.util.* class MapboxNavigationViewActivity : ReactActivity() { private val mapboxNavigation: MapboxNavigation by requireMapboxNavigation( onResumedObserver = object : MapboxNavigationObserver { @SuppressLint("MissingPermission") override fun onAttached(mapboxNavigation: MapboxNavigation) { } override fun onDetached(mapboxNavigation: MapboxNavigation) { } }, ) @CallSuper override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Log.d("This activity launches now!") setContentView(R.layout.mapbox_activity_navigation_view) mapboxNavigation.startTripSession() } }