Can not add Android Native Module to React Native project

2.1k Views Asked by At

I have a task where I need to add a Native functionality to the React Native project for Android devices.

I have followed this [tutorial][1], which is pretty straight-forward. But unfortunately, I still can not access my Native functionality from JS layer.

Here's how my code looks like.

SimpleTestModule.java:

package com.reactnativesampleapp;

import android.util.Log;

import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

public class SimpleTestModule extends ReactContextBaseJavaModule {

    SimpleTestModule(ReactApplicationContext context) {
        super(context);
    }

    @Override
    public String getName() {
        return "SimpleTestModule";
    }

    @ReactMethod
    public void simplePublicMethod() {
        Log.d("SimpleTestModule", "Some cool log!");
    }
}

SimpleTestPackage.java:

package com.reactnativesampleapp;

import android.util.Log;

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 SimpleTestPackage implements ReactPackage {

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

    @Override
    public List<NativeModule> createNativeModules(
            ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();

        modules.add(new SimpleTestModule(reactContext));
        Log.d("SimpleTestPackage", "Log just to check if this code gets executed");

        return modules;
    }
}

And finally, Here's my MainApplication.java:

package com.reactnativesampleapp;

import android.app.Application;
import android.content.Context;
import android.net.Uri;
import android.util.Log;

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.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;
import com.makeupreactnativesampleapp.generated.BasePackageList;

import org.unimodules.adapters.react.ReactAdapterPackage;
import org.unimodules.adapters.react.ModuleRegistryAdapter;
import org.unimodules.adapters.react.ReactModuleRegistryProvider;
import org.unimodules.core.interfaces.Package;
import org.unimodules.core.interfaces.SingletonModule;
import expo.modules.constants.ConstantsPackage;
import expo.modules.permissions.PermissionsPackage;
import expo.modules.filesystem.FileSystemPackage;
import expo.modules.updates.UpdatesController;

import com.facebook.react.bridge.JSIModulePackage;
import com.swmansion.reanimated.ReanimatedJSIModulePackage;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nullable;

public class MainApplication extends Application implements ReactApplication {
  private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider(
    new BasePackageList().getPackageList()
  );

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

    @Override
    protected List<ReactPackage> getPackages() {
      Log.d("Eugene", "getPackages!");
      List<ReactPackage> packages = new PackageList(this).getPackages();
      packages.add(new ModuleRegistryAdapter(mModuleRegistryProvider));
      packages.add(new SimpleTestPackage());
      return packages;
    }

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

    @Override
    protected JSIModulePackage getJSIModulePackage() {
      return new ReanimatedJSIModulePackage();
    }

    @Override
    protected @Nullable String getJSBundleFile() {
      if (BuildConfig.DEBUG) {
        return super.getJSBundleFile();
      } else {
        return UpdatesController.getInstance().getLaunchAssetFile();
      }
    }

    @Override
    protected @Nullable String getBundleAssetName() {
      if (BuildConfig.DEBUG) {
        return super.getBundleAssetName();
      } else {
        return UpdatesController.getInstance().getBundleAssetName();
      }
    }
  };

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

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

    if (!BuildConfig.DEBUG) {
      UpdatesController.initialize(this);
    }

    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.makeupreactnativesampleapp.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();
      }
    }
  }
}

And here's a JS file where I use my Native module:

import React from 'react';

import { 
  StyleSheet, 
  SafeAreaView, 
  NativeModules } from 'react-native';

const { SimpleTestModule } = NativeModules;

export default function App() {

  SimpleTestModule.simplePublicMethod();
  return (
    <SafeAreaView style={styles.container}>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: "column",
    backgroundColor: "#fff",
    justifyContent: "space-around",
    alignItems: "center",
    paddingTop: Platform.OS === "android" ? StatusBar.currentHeight : 0,
  });

After adding everything, I clean Android project via ./gradlew clean and run npm run android

Few notes:

  1. I have put a log into my Package class just to see if it even gets executed. Unfortunately, log never appeared so I guess it doesn't get executed
  2. When I run npm run android, compilation is successful, but .apk is failing to launch on the device automatically. Can that be a signal that I have serious issues in my setup?

Here's a log at the end of npm run android execution:

BUILD SUCCESSFUL in 49s
508 actionable tasks: 508 executed
info Connecting to the development server...
warn Failed to connect to development server using "adb reverse": spawnSync adb ENOENT
info Starting the app...
error Failed to start the app. Run CLI with --verbose flag for more details.
Error: spawnSync adb ENOENT
    at Object.spawnSync (node:internal/child_process:1086:20)

  [1]: https://reactnative.dev/docs/native-modules-android
  1. When I run Android app on the device through Expo, I have such logs when the app gets launched:

` Error: Unable to resolve module ./debugger-ui/debuggerWorker.aca173c4 from /Users/superyevhen/Documents/Work/Resources/releases/MakeupReactNativeSampleApp/.:

None of these files exist:
  * debugger-ui/debuggerWorker.aca173c4(.native|.native.ts|.ts|.native.tsx|.tsx|.native.js|.js|.native.jsx|.jsx|.native.json|.json)
  * debugger-ui/debuggerWorker.aca173c4/index(.native|.native.ts|.ts|.native.tsx|.tsx|.native.js|.js|.native.jsx|.jsx|.native.json|.json)
    at ModuleResolver.resolveDependency (/Users/superyevhen/Documents/Work/Resources/releases/MakeupReactNativeSampleApp/node_modules/metro/src/node-haste/DependencyGraph/ModuleResolution.js:168:15)
    at DependencyGraph.resolveDependency (/Users/superyevhen/Documents/Work/Resources/releases/MakeupReactNativeSampleApp/node_modules/metro/src/node-haste/DependencyGraph.js:353:43)
    at /Users/superyevhen/Documents/Work/Resources/releases/MakeupReactNativeSampleApp/node_modules/metro/src/lib/transformHelpers.js:271:42
    at /Users/superyevhen/Documents/Work/Resources/releases/MakeupReactNativeSampleApp/node_modules/metro/src/Server.js:1097:37
    at Generator.next (<anonymous>)
    at asyncGeneratorStep (/Users/superyevhen/Documents/Work/Resources/releases/MakeupReactNativeSampleApp/node_modules/metro/src/Server.js:99:24)
    at _next (/Users/superyevhen/Documents/Work/Resources/releases/MakeupReactNativeSampleApp/node_modules/metro/src/Server.js:119:9)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
Error: Unable to resolve module ./debugger-ui/debuggerWorker.aca173c4 from /Users/superyevhen/Documents/Work/Resources/releases/MakeupReactNativeSampleApp/.:`

This message repeats multiple times (7 times actually), but the app is working. I still can modify JS layer successfully and see changes, but my native modules are still not present. Can that be a root cause of an issue?

Any help would be appreciated. I have tried to be as precise as possible but if you need any additional info in order to share an expertise, please let me know. Thanks in advance!

1

There are 1 best solutions below

2
On

There seems a problem with your MainApplication.java. I tried with your file it was having some undefined params.
Try this if it works for you.

package com.reactnativesampleapp;

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.add(new SimpleTestPackage());
          // Packages that cannot be autolinked yet can be added manually here, for example:
          // packages.add(new MyReactNativePackage());
          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.reactnativesampleapp.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();
      }
    }
  }
}