javareact-nativereact-native-native-modulenative-module

Native module in React Native: function returns "undefined"


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?


Solution

  • 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);
    }