4
votes

I have a React Native app, that was built using Expo, which I then ejected to ExpoKit.

I followed this tutorial to try to implement a custom native Android module, with a few changes due to the layout of an ejected ExpoKit app. Thus, the android directory is laid out as follows:

android directory

// <workspace>/android/app/src/main/java/com/reactlibrary/ToastModule.java

package com.reactlibrary;

import android.widget.Toast;

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;

import java.util.Map;
import java.util.HashMap;

public class ToastModule extends ReactContextBaseJavaModule {

    private static final String DURATION_SHORT_KEY = "SHORT";
    private static final String DURATION_LONG_KEY = "LONG";

    public ToastModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

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

    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();
        constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
        constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
        return constants;
    }

    @ReactMethod
    public void show(String message, int duration) {
        Toast.makeText(getReactApplicationContext(), message, duration).show();
    }

}
// <workspace>/android/app/src/main/java/com/reactlibrary/ToastPackage.java

package com.reactlibrary;

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 CustomToastPackage 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 ToastModule(reactContext));

        return modules;
    }

}
// <workspace>/android/app/src/main/java/host/exp/exponent/MainApplication.java

package host.exp.exponent;

// other imports

import com.reactlibrary.CustomToastPackage

public class MainApplication extends ExpoApplication implements AppLoaderPackagesProviderInterface<ReactPackage> {

  // Needed for `react-native link`
  public List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(
        // Needed for `react-native link`
        new MainReactPackage(),

        new KCKeepAwakePackage(),
        new CustomToastPackage()
    );
  }

  // other methods...
}

Finally, in my React Native application, I load it like so:

// <workspace>/src/utils/native-modules.ts

import { NativeModules } from 'react-native'

export const { ToastExample } = NativeModules

Expected result

I would expect ToastExample in native-modules.ts to return some API that I can interact with, in this case, the methods exposed by the CustomToastPackage.

Actual result

ToastExample is undefined.

2

2 Answers

0
votes

Please make a module declaration on the same path.

// <workspace>/android/app/src/main/java/com/reactlibrary/index.js
import { NativeModules } from "react-native";

const { ToastExample } = NativeModules;

export default ToastExample;

Usage

import {
  NativeModules
} from "react-native";
...
const { ToastExample } = NativeModules;
...
ToastExample.show('Awesome', ToastExample.SHORT);
0
votes

Judging from your code, you custom package creation looks correct. Assuming the android code is also built before running the app (not just a package reload from the bundler), I would expect this to work.

You do not show an actual method request on ToastExample, so at which point did you determine that ToastExample is undefined? Did you use a debugger for this?

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

showTest = () => {
  ToastExample.show('test', ToastExample.SHORT);
}

If you'd set a breakpoint inside the showTest function, it's possible a debugger returns a ReferenceError when evaluating the 'ToastExample' inside a debug console, while the code runs fine. So did the code actually break at run-time?