react-native icon indicating copy to clipboard operation
react-native copied to clipboard

Android Native UI Module: "View config getter callback for component `MyNativeModule` must be a function (received `undefined`)."

Open TheWirv opened this issue 3 years ago • 8 comments

Description

Hey all, so in lack of any real onFocus/onBlur functionality (except for TextInput obv), I've been trying to build my own component as a native module. I've been following both the docs for plain native modules and UI modules, I used create-react-native-library to scaffold the project (in "Obj-C+Java" config), and you can see my current progress so far below.

The problem is the following: When I'm trying to run the app (after building the Android app and the JS bundle), I get the following error message: Invariant Violation: View config getter callback for component `OnChangeFocusView` must be a function (received `undefined`).

I dug around a little and found out that ReactNativeViewConfigRegistry.register is throwing this invariant violation. To test out what's going on, I threw a couple of console.logs in there, and it seems like something is really f***ed up because the error complains that register has received undefined for its callback argument, while my logs show that it was not undefined, and indeed of type function (see images below).

I also looked to the React Native Gesture Handler library for inspiration (especially their RNGestureHandlerRootViewManager.java and RNGestureHandlerRootView.java classes), but I can't see anything they do that I don't.

Man..and I haven't even gotten to iOS, yet.

createReactNativeComponentClass.js createReactNativeComponentClass js

ReactNativeViewConfigRegistry.js ReactNativeViewConfigRegistry js

debugger-ui debugger-ui

React Native version:

Run react-native info in your terminal and copy the results here.

RN Info
System:
    OS: Windows 10 10.0.19042
    CPU: (8) x64 Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz
    Memory: 475.23 MB / 15.85 GB
  Binaries:
    Node: 15.12.0 - ~\AppData\Local\Temp\yarn--1620743931304-0.6946422970609345\node.CMD
    Yarn: 1.22.5 - ~\AppData\Local\Temp\yarn--1620743931304-0.6946422970609345\yarn.CMD
    npm: 7.6.3 - C:\Program Files\nodejs\npm.CMD
    Watchman: Not Found
  SDKs:
    Android SDK:
      API Levels: 23, 25, 26, 27, 28, 29, 30
      Build Tools: 28.0.3, 29.0.0, 29.0.1, 29.0.2, 29.0.3, 30.0.0, 30.0.1, 30.0.2, 30.0.3, 31.0.0, 31.0.0
      System Images: android-18 | Google APIs Intel x86 Atom, android-19 | Google APIs Intel x86 Atom, android-27 | Google Play Intel x86 Atom, android-28 | Intel x86 Atom, android-28 | Intel x86 Atom_64, android-28 | Google APIs Intel x86 Atom, android-28 | Google APIs Intel x86 Atom_64, android-28 | Google Play Intel x86 Atom, android-28 | Google Play Intel x86 Atom_64, android-29 | Intel x86 Atom_64, android-29 | Google APIs Intel x86 Atom, android-29 | Google APIs Intel x86 Atom_64
      Android NDK: Not Found
    Windows SDK:
      AllowAllTrustedApps: Disabled
      Versions: 10.0.14393.0, 10.0.19041.0
  IDEs:
    Android Studio: Version  4.2.0.0 AI-202.7660.26.42.7322048
    Visual Studio: 16.9.31205.134 (Visual Studio Enterprise 2019)
  Languages:
    Java: 13.0.2 - C:\Program Files\Java\jdk-13.0.2\bin\javac.EXE
  npmPackages:
    @react-native-community/cli: Not Found
    react: 17.0.2 => 17.0.2
    react-native: 0.64.1 => 0.64.1
    react-native-windows: Not Found
  npmGlobalPackages:
    *react-native*: Not Found

Steps To Reproduce

Provide a detailed list of steps that reproduce the issue.

  1. Create a new native module via create-react-native-library
  2. Set it up like I did
  3. Run the app

Expected Results

The app is supposed to run and show my component. :(

Snack, code example, screenshot, or link to a repository:

Here's my code so far Edit: I have published a version of this code to GitHub: https://github.com/TheWirv/rn-native-module-test

Code

NativeModulesPackage.java

package com.my.package;

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.Arrays;
import java.util.Collections;
import java.util.List;


public class NativeModulesPackage implements ReactPackage {
  @Override
  public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
    return Collections.emptyList();
  }

  @Override
  public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
    return Arrays.<ViewManager>asList(new OnChangeFocusViewManager());
  }
}

OnChangeFocusViewManager.java

package com.my.package;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.facebook.react.common.MapBuilder;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;

import java.util.Map;

@ReactModule(name = OnChangeFocusViewManager.REACT_CLASS)
public class OnChangeFocusViewManager extends ViewGroupManager<OnChangeFocusView> {
    public static final String REACT_CLASS = "OnChangeFocusView";

    @Override
    @NonNull
    public String getName() {
        return REACT_CLASS;
    }

    @Override
    @NonNull
    public OnChangeFocusView createViewInstance(@NonNull ThemedReactContext context) {
        return new OnChangeFocusView(context);
    }

    @Override
    public @Nullable
    Map<String, Object> getExportedCustomDirectEventTypeConstants() {
        return MapBuilder.of(
                "onFocus",
                MapBuilder.of("phasedRegistrationNames", "onFocus"),
                "onBlur",
                MapBuilder.of("phasedRegistrationNames", "onBlur"));
    }
}

OnChangeFocusView.java

package com.my.package;

import android.content.Context;
import android.view.View;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.facebook.react.views.view.ReactViewGroup;

public class OnChangeFocusView extends ReactViewGroup implements View.OnFocusChangeListener {
    public OnChangeFocusView(Context context) {
        super(context);
    }

    private void dispatchOnBlur() {
        WritableMap event = Arguments.createMap();
        ReactContext reactContext = (ReactContext) getContext();
        reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "onBlur", event);
    }

    private void dispatchOnFocus() {
        WritableMap event = Arguments.createMap();
        ReactContext reactContext = (ReactContext) getContext();
        reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "onFocus", event);
    }

    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        if (hasFocus) {
            dispatchOnFocus();
        } else {
            dispatchOnBlur();
        }
    }
}

index.tsx

import {requireNativeComponent} from 'react-native';
// Types
import type {ViewStyle} from 'react-native';
import type {PropsWithChildren} from 'react';

type OnChangeFocusViewProps = PropsWithChildren<{
  onBlur?: () => void;
  onFocus?: () => void;
  style?: ViewStyle;
}>;

export const OnChangeFocusView =
  requireNativeComponent<OnChangeFocusViewProps>('OnChangeFocusView');

example/src/App.tsx

import * as React from 'react';

import {View, Text, StyleSheet} from 'react-native';
import {OnChangeFocusView} from 'my-native-modules';

export default function App() {
  return (
    <View style={styles.container}>
      <OnChangeFocusView
        onBlur={() => console.log('Blurred')}
        onFocus={() => console.log('Focused')}
        style={styles.box}>
        <Text>Touch Me!</Text>
      </OnChangeFocusView>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: 'white',
  },
  box: {
    width: 60,
    height: 60,
    marginVertical: 20,
    backgroundColor: 'red',
  },
});

inside example/android/app/src/main/java/com/example/nativemodules/MainApplication.java

import com.my.package.NativeModulesPackage;
[...]
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 NativeModulesExample:
  // packages.add(new MyReactNativePackage());
  packages.add(new NativeModulesPackage());
  return packages;
}

TheWirv avatar May 11 '21 15:05 TheWirv