question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

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

See original GitHub issue

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

Issue Analytics

  • State:open
  • Created 2 years ago
  • Comments:5

github_iconTop GitHub Comments

11reactions
alyccommented, Aug 24, 2021

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 😉

2reactions
mateusz1913commented, Jul 4, 2021

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)

Read more comments on GitHub >

github_iconTop Results From Across the Web

Android Native UI Components
Fortunately, we can wrap up these existing components for seamless integration with your React Native application. Like the native module guide, ...
Read more >
React native UI is not getting rendered after callback from ...
That is not how to use NativeEventEmitter. You need to initialise the NativeEventEmitter with the native module you are emitting events from ...
Read more >
Building React Native UI Components and Modules in 2021
In a recent assignment, I had occasion to build a wrapper around two separate proprietary video players for iOS and Android respectively. The ......
Read more >
Bridging Native UI Components in the React Native
The native view, the one with gray background, contains 3 UI elements: ... iOS and Android modules as well as JS files and...
Read more >
Writing a native module for React Native using Kotlin
We're about to build a react-native-utube-player, a Native UI component that allows playing YouTube videos in our React Native app.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found