Skip to content

[Android][v3] GestureHandlerRootView reads globalThis._RNGH_MODULE_ID without ensuring the native module is initialized #4523

Description

@ArturKalach

Description

After updating to RNGH v3 we started getting crashes in our production Android build, with:

minifyEnabled true
shrinkResources true

The crash is similar to:

Image

Debug builds are fine, only the minified release build crashes. Logs pointed us to globalThis._RNGH_MODULE_ID and to how RNGestureHandlerModule gets initialized on Android.


What we found

_RNGH_MODULE_ID is set on the JSI global from C++, in RNGHRuntimeDecorator::installRNRuntimeBindings:

// shared/runtime/RNGHRuntimeDecorator.cpp:87-89
auto moduleIdValue = jsi::Value(moduleId);
rnRuntime.global().setProperty(rnRuntime, "_RNGH_MODULE_ID", std::move(moduleIdValue));

On Android this runs from the bindings installer returned by the Kotlin module (android/src/main/jni/RNGestureHandlerModule.cpp:34-42), so it happens only after RNGestureHandlerModule is constructed.

TurboModules are lazy, and as far as I can see the only thing that asks for this one is the module scope call in the codegen spec:

// src/specs/NativeRNGestureHandlerModule.ts:40
export default TurboModuleRegistry.getEnforcing<Spec>('RNGestureHandlerModule');

And the root view reads the global directly at render:

// src/components/GestureHandlerRootView.android.tsx:21
moduleId={globalThis._RNGH_MODULE_ID} // Ensure moduleId is set

So the component depends on some other module having imported the spec first. I could not find a place where this order is actually guaranteed, but maybe I am looking in the wrong place and there is something that ensures it that I missed.

If the global is still undefined at that moment, codegen applies the WithDefault<Int32, -1> default, native gets -1, and then:

// android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt:30
RNGestureHandlerModule.registries[moduleId] ?: throw Exception("Tried to access a non-existent registry")

Our theory for the release build is that with a lot of JS and more work during startup, plus minification and resource shrinking, the module initialization does not finish before the first render of GestureHandlerRootView. We could not confirm the exact timing, so this part is still a guess.


Environment

  • react-native-gesture-handler: 3.3.0
  • react-native: 0.87.1
  • react: 19.2.3
  • Platform: Android
  • Architecture: new arch (newArchEnabled=true)
  • JS engine: Hermes
  • Fails with minifyEnabled true + shrinkResources true in release

Steps to reproduce

To make it visible in a clean project without minification, we import GestureHandlerRootView directly by its path instead of from the package root:

import * as React from 'react';
import { Text } from 'react-native';
import GestureHandlerRootView from 'react-native-gesture-handler/lib/module/components/GestureHandlerRootView';

export default function App() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <Text>hello</Text>
    </GestureHandlerRootView>
  );
}

On Android this crashes every time, also in debug.

I know deep import is not public API and nobody should write this in a real app. We use the normal import in our app. It is only a way to show the same end state in a simple example, because it removes everything else that the package root pulls in.

The import graph shows that lib/module/index.js has NativeRNGestureHandlerModule, however in some cases it's somehow skipped or removed, which could cause an issue with GestureHandlerRootView.

A link to a Gist, an Expo Snack or a link to a repository based on this template that reproduces the bug.

https://github.com/ArturKalach/rngh3-android-module-id-repro

Gesture Handler version

3.3.0

React Native version

0.87.1

Platforms

Android

JavaScript runtime

Hermes

Workflow

React Native (without Expo)

Architecture

New Architecture (Fabric)

Build type

Release mode

Device

Android emulator

Device model

Android Emulator, Real Device

Acknowledgements

Yes

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions