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

Hey @TheWirv, I had the same issue, but on iOS side. The problem was with incorrectly blocked react-native resolution in my example/metro.config.js.

I had to update react-native version in my library from 0.63.4 to 0.64.2, which resulted in newer version of metro-config. And that newer version had different path and argument names (exclusionList, blockList instead of blacklist)

My problem was that I incorrectly migrated resolver argument in metro config

What was causing problem

const exclusionList = require('metro-config/src/defaults/exclusionList');

//...

resolver: {
    blocklist: exclusionList( // <----- this line
      modules.map(
        (m) =>
          new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`)
      )
    ),

    extraNodeModules: modules.reduce((acc, name) => {
      acc[name] = path.join(__dirname, 'node_modules', name);
      return acc;
    }, {}),
  },

Instead of:

 blocklist: exclusionList

There should be (capital 'L')

 blockList: exclusionList

Make sure that your metro config is correct, because otherwise, view config won't be registered to correct react-native package (the one that will be executed by app)

mateusz1913 avatar Jul 04 '21 10:07 mateusz1913

@TheWirv Were you able to solve the issue?

@mateusz1913 I'm also facing the same problem for iOS, but your proposal also failed for me. I'm trying to setup correct example for react-native-video, that will use parent directory instead of installed package. Can you provide me with your setup for example & library itself?

wishfulthinkerme avatar Jul 27 '21 14:07 wishfulthinkerme

@wishfulthinkerme look for react-native-avoid-softinput and check git history for example/metro.config.js file

mateusz1913 avatar Jul 28 '21 07:07 mateusz1913

Damn it is still the same. It seems that NativeModule is exported correctly, but for Native UI Components I still receive: View config getter callback for component ...

wishfulthinkerme avatar Jul 28 '21 09:07 wishfulthinkerme

For my case, I have to remove the node_modules directory within the MyNativeModule before import, else it will be loaded and this creates another "module dictionary", beside the existing one belonging to the app

MyNativeModule will be loaded by default in the new module dictionary. But when the app search for the module, it will only search in the module dictionary that belongs to the app, hence that is why it returns undefined.

We have to remove the node_modules within the MyNativeModule to force it to load to the app's module dictionary in order to allow it be found.

if you need to retain the node_modules in the MyNativeModule for further development, then you will need to remove it directly within the imported directory of the app's node_modules folder after the import.

  • app
    • node_modules
      • MyNativeModule
        • node_modules <-- remove this

Sorry if the term "module dictionary" is inaccurate and confusing here, let me know the term that I should use.

Hope it helps 😉

alyc avatar Aug 24 '21 06:08 alyc

node_modules will be recreated again in the next npm install? @alyc

arled avatar Oct 30 '23 15:10 arled

Also reproduced when one of mono repo package has alternative react-native version of application. Be careful! And check all package.json for react-native version package.

mikalaiulasevich avatar Nov 14 '23 15:11 mikalaiulasevich

This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.

github-actions[bot] avatar May 13 '24 05:05 github-actions[bot]

This issue was closed because it has been stalled for 7 days with no activity.

github-actions[bot] avatar May 20 '24 05:05 github-actions[bot]