10

So, I am practicing making native modules in Java that can have functions exposed to the JavaScript code of React Native. For that, I decided to go for a simple demo math library. My code is as follows.

MathOpsModule.java

package com.mb_rn_poc;

import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

public class MathOpsModule extends ReactContextBaseJavaModule {

  @Override
  public String getName() {
    return "MathOps"; // Name of the Native Modules.
  }

  @ReactMethod
  public int add(int a, int b) {
    return (a + b);
  }
}

MathOpsPackage.java

package com.mb_rn_poc;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class MathOpsPackage implements ReactPackage {

@Override
public List<ViewManager> createViewManagers(
  ReactApplicationContext reactContext
) {
    return Collections.emptyList();
}

@Override
public List<NativeModule> createNativeModules(
      ReactApplicationContext reactContext
) {
    List<NativeModule> modules = new ArrayList<>();
    // Register the MathOps module
    modules.add(new MathOpsModule());
    return modules;
  }
}

MainApplication.java

package com.mb_rn_poc;

import android.app.Application;
import android.content.Context;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
import java.lang.reflect.InvocationTargetException;
import java.util.List;

public class MainApplication extends Application implements ReactApplication {

  private final ReactNativeHost mReactNativeHost =
      new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
          return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List<ReactPackage> packages = new PackageList(this).getPackages();
          // Packages that cannot be autolinked yet can be added manually here, for example:
          // packages.add(new MyReactNativePackage());
          packages.add(new MathOpsPackage());
          return packages;
        }

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

  @Override
  public ReactNativeHost getReactNativeHost() {
    return mReactNativeHost;
  }

  @Override
  public void onCreate() {
    super.onCreate();
    SoLoader.init(this, /* native exopackage */ false);
    initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
  }

  /**
   * Loads Flipper in React Native templates. Call this in the onCreate method with something like
   * initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
   *
   * @param context
   * @param reactInstanceManager
   */
  private static void initializeFlipper(
      Context context, ReactInstanceManager reactInstanceManager) {
    if (BuildConfig.DEBUG) {
      try {
        /*
         We use reflection here to pick up the class that initializes Flipper,
        since Flipper library is not available in release mode
        */
        Class<?> aClass = Class.forName("com.mb_rn_poc.ReactNativeFlipper");
        aClass
            .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
            .invoke(null, context, reactInstanceManager);
      } catch (ClassNotFoundException e) {
        e.printStackTrace();
      } catch (NoSuchMethodException e) {
        e.printStackTrace();
      } catch (IllegalAccessException e) {
        e.printStackTrace();
      } catch (InvocationTargetException e) {
        e.printStackTrace();
      }
    }
  }
}

Now, on the React Native side of things, I have these two files -

MathOps.js

import { NativeModules } from 'react-native';

const MathOps = NativeModules.MathOps;

export const add = (a, b) => {
    return MathOps.add(a, b);
}

MainApp.js

import React, { useEffect, useState } from 'react';
import { SafeAreaView, Text, View } from 'react-native';
import { add } from "./NativeWrapper/MathOps";

const MainApp = () => {

    const [state, setState] = useState(0);

    useEffect(() => {
        let sum = add(10, 12);
        console.log({ sum });
        setState(sum);

    }, [])

    return (
        <SafeAreaView>
            <View>
                <Text>
                    {state}
                </Text>
            </View>
        </SafeAreaView>
    );
}

export default MainApp;

The problem is, the add function is returning undefined. As such, nothing gets printed on the screen. Any idea what I might be doing wrong?

3 Answers 3

12

Calls to native methods are asynchronous, so they cannot return values directly.

You can either use a callback (as you figured out yourself), or a Promise to allow for the more elegant async / await syntax (and more):

@ReactMethod
public void add(Double a, Double b, Promise promise) {
    Double sum = a + b;
    promise.resolve(sum);
}
import { NativeModules } from 'react-native';

const { MathOps } = NativeModules;

export const add = async (a, b) => {
    return await MathOps.add(a, b);
}
Sign up to request clarification or add additional context in comments.

Comments

1

React Native bridge is asynchronous. We can pass data from Native Side to Javascript in following ways:-

1) Promise: When the last parameter of a native module Java/Kotlin method is a Promise, its corresponding JS method will return a JS Promise object.

In Module.java->

@ReactMethod
public void add(Double a, Double b, Promise promise) {            
        Double sum = a + b;
        promise.resolve(sum);
}       

This is how you will then access in Javascript->

import { NativeModules } from 'react-native'; 
const { MathOps } = NativeModules;

export const add = async (a, b) => {
    return await MathOps.add(a, b);
}

2. Callback: They can also be used to asynchronously execute JavaScript from the native side.

In Module.java->

@ReactMethod
public void add(Double a, Double b, Callback cb) {            
        Double sum = a + b;
        cb.invoke(sum);
} 

This is how you will then access in Javascript->

import { NativeModules } from 'react-native'; 
const { MathOps } = NativeModules;

useEffect(() => {
  MathOps.add(10, 12, (sum) => {
    setState(sum);
  });
}, [])

3. isBlockingSynchronousMethod (Not Recommended due to performance penalties and threading-related bugs to your native modules): You can pass isBlockingSynchronousMethod = true to a native method to mark it as a synchronous method.

@ReactMethod(isBlockingSynchronousMethod = true)
public Double add(Double a, Double b){
    Double sum = a + b;
    return sum;
}

In js file:

import { NativeModules } from 'react-native'; 
const { MathOps } = NativeModules;

export const add = async (a, b) => {
    return await MathOps.add(a, b);
}

4. Emitting Events: haven't explored it much.

Comments

0

Not sure what the issue with this is, but there is a workaround - using Callbacks, as described in https://reactnative.dev/docs/native-modules-android. I made the following modifications -

@ReactMethod
public void add(Double a, Double b, Callback cb) {
    Double sum = a + b;
    cb.invoke(sum);
}
import { /* ... */ NativeModules } from 'react-native';
const { MathModule } = NativeModules;

// ...
// ...

MathModule.add(10, 20, (sum) => {
    alert(sum);
})

And it does what I expect it to - alerts "30".

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.