Send Events from Kotlin to React Native JavaScript Engine
Send events from Kotlin code to JS Engine using Native Modules in React Native Android app
Suyash Singh
Posted by Suyash Singh
on April 28, 2024
Photo by Anton on Unsplash

In this post, we will demonstrate how to send events from Kotlin to the JavaScript engine using native modules in a React Native app. We will use a simple example where the backend calculates temperature using a sensor and the React Native app displays it.

Essentially, we need to emit messages to the DeviceEventManagerModule.RCTDeviceEventEmitter class which passes the messages to the JS Instance using the React Native Bridge.

Step-by-Step Guide

1. Setting Up the Kotlin Module

Create a Kotlin file BackendModule.kt with the following code:

In this post we are going to create a BackendModule.kt file, which should be registered as a native module in our react native app. You can learn to create and register custom native modules packages here.

/android/app/src/main/java/com/sampleapp/BackendModule.kt
 
package com.sampleapp
import kotlin.random.Random
import com.facebook.react.bridge.Promise
import android.util.Log
import com.sampleapp.uniffi.rustyHello
import com.sampleapp.uniffi.rustyInit
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.WritableMap
import com.facebook.react.modules.core.DeviceEventManagerModule
 
class BackendModule internal constructor(context: ReactApplicationContext?) : ReactContextBaseJavaModule(context) {
 
    override fun getName(): String {
        return "BackendModule"
    }
 
    private var listenerCount = 0
    val TEMPERATURE_SENSOR = "TEMPERATURE_SENSOR"
    
    @ReactMethod
    fun recordTemperatures() {
        updateTemperature()
    }
 
    @ReactMethod
    fun addListener(_eventName: String) {
        listenerCount += 1
    }
 
    @ReactMethod
    fun removeListeners(count: Int) {
        listenerCount -= count
    }
 
    fun updateTemperature() {
        Thread {
            while (true) {
                readTemperature()
                Thread.sleep(1000)
            }
        }
        .start()
    }
 
    private fun sendEvent(reactContext: ReactContext, eventName: String, params: WritableMap?) {
        reactContext
            .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
            .emit(eventName, params)
    }
 
    // Function to generate a random temperature between 5 and 50
    private fun getTemperature(): Int {
        return Random.nextInt(5, 51)  // Generates a random integer between 5 (inclusive) and 51 (exclusive)
    }
 
    // Function to read temperature data and send it as an event
    private fun readTemperature() {
        val temperature = getTemperature()
 
        val params = Arguments.createMap().apply {
            putInt("temperature", temperature)
        }
 
        sendEvent(getReactApplicationContext(), TEMPERATURE_SENSOR, params)
    }
}

2. Setting Up the React Native Component

Now we need to create a React Native component which can subscribe to these events emitted by our BackendModule Native Module.

App.tsx
import React, {useEffect, useState} from 'react';
import {
  NativeEventEmitter,
  NativeModules,
  SafeAreaView,
  Text,
  View,
} from 'react-native';
 
const {BackendModule} = NativeModules;
 
const Temperature = () => {
  const [temperature, setTemperature] = useState(0);
 
  const eventEmitter = new NativeEventEmitter(NativeModules.BackendModule);
 
  useEffect(() => {
    const eventListener = eventEmitter.addListener(
      'TEMPERATURE_SENSOR',
      event => {
        setTemperature(event.temperature);
      },
    );
 
    BackendModule.recordTemperatures();
 
    return () => {
      eventListener.remove();
    };
  }, []);
 
  return (
    <View className="h-screen w-screen items-center justify-center">
      <Text className="text-6xl">{`${temperature} ° C`}</Text>
    </View>
  );
};
 
function App(): JSX.Element {
  return (
    <SafeAreaView>
      <Temperature />
    </SafeAreaView>
  );
}
 
export default App;

In this post we are using TailwindCSS with react native. Learn how to set it up here in a previous post I have written on it.

Explanation

BackendModule.kt:

  • getTemperature(): Generates a random temperature between 5 and 50.
  • readTemperature(): Reads the temperature and sends it as an event to the JavaScript engine.
  • sendEvent(): Sends the temperature event to the JavaScript engine using RCTDeviceEventEmitter.
  • recordTemperatures(): Starts a thread that continuously reads the temperature every second and sends it as an event.

App.tsx:

Temperature Component:

  • Sets up a useEffect hook to listen for temperature events from the backend.
  • Calls BackendModule.recordTemperatures() to start recording temperatures.
  • Updates the temperature state when a new event is received and displays it.

Conclusion

This example demonstrates how to use native modules in a React Native app to send events from Kotlin to the JavaScript instance. By following these steps, you can set up your backend to calculate data (e.g., temperature from a sensor) and display it in your React Native app. This approach can be extended to handle more complex interactions between your native code and React Native app.