facebook / react-native

A framework for building native applications using React
https://reactnative.dev
MIT License
119.06k stars 24.31k forks source link

UIImplementation.createView "Root node with tag 21 doesn't exist " for both debug builds and prod builds #41518

Closed simonxcheng closed 2 months ago

simonxcheng commented 11 months ago

Description

If an apk is debug enabled, one out of 3 times, the first load on physical devices (Pixel 5, Samsung S20 Ultra, etc.) cause errors due to "no active CatalystInstance". RN error screen also shows "Root node with tag 21 doesn't exist". It only happens on the first load. Sub-sequential loads have no issue. [Update: we observed it is repeated issue on production!]

First error call stack:

Unhandled SoftException com.facebook.react.bridge.ReactNoCrashSoftException: No active CatalystInstance, cannot emitUpdateDimensionsEvent at com.facebook.react.modules.deviceinfo.DeviceInfoModule.emitUpdateDimensionsEvent(DeviceInfoModule.java:94) at com.facebook.react.ReactRootView$CustomGlobalLayoutListener.emitUpdateDimensionsEvent(ReactRootView.java:1075) at com.facebook.react.ReactRootView$CustomGlobalLayoutListener.checkForDeviceDimensionsChanges(ReactRootView.java:1032) at com.facebook.react.ReactRootView$CustomGlobalLayoutListener.onGlobalLayout(ReactRootView.java:916) at android.view.ViewTreeObserver.dispatchOnGlobalLayout(ViewTreeObserver.java:1084) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:4302) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:3116) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:10885) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1301) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1309) at android.view.Choreographer.doCallbacks(Choreographer.java:923) at android.view.Choreographer.doFrame(Choreographer.java:852) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1283) at android.os.Handler.handleCallback(Handler.java:942) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:226) at android.os.Looper.loop(Looper.java:313) at android.app.ActivityThread.main(ActivityThread.java:8762) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:604) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)

Second error call stack:

com.facebook.react.bridge.ReactNoCrashSoftException: Cannot get UIManager because the context doesn't contain an active CatalystInstance. at com.facebook.react.uimanager.UIManagerHelper.getUIManager(UIManagerHelper.java:77) at com.facebook.react.uimanager.UIManagerHelper.getEventDispatcher(UIManagerHelper.java:128) at com.facebook.react.uimanager.UIManagerHelper.getEventDispatcherForReactTag(UIManagerHelper.java:106) at com.th3rdwave.safeareacontext.SafeAreaProviderManagerKt.handleOnInsetsChange(SafeAreaProviderManager.kt:39) at com.th3rdwave.safeareacontext.SafeAreaProviderManagerKt.access$handleOnInsetsChange(SafeAreaProviderManager.kt:1) at com.th3rdwave.safeareacontext.SafeAreaProviderManager$addEventEmitters$1.invoke(SafeAreaProviderManager.kt:28) at com.th3rdwave.safeareacontext.SafeAreaProviderManager$addEventEmitters$1.invoke(SafeAreaProviderManager.kt:28) at com.th3rdwave.safeareacontext.SafeAreaProvider.maybeUpdateInsets(SafeAreaProvider.kt:21) at com.th3rdwave.safeareacontext.SafeAreaProvider.onPreDraw(SafeAreaProvider.kt:39) at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:1121) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:4442) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:3116) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:10885) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1301) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1309) at android.view.Choreographer.doCallbacks(Choreographer.java:923) at android.view.Choreographer.doFrame(Choreographer.java:852) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1283) at android.os.Handler.handleCallback(Handler.java:942) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:226) at android.os.Looper.loop(Looper.java:313) at android.app.ActivityThread.main(ActivityThread.java:8762) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:604) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)

React Native Version

0.72.6

Output of npx react-native info

System: OS: macOS 13.5.1 CPU: (10) arm64 Apple M1 Pro Memory: 78.66 MB / 16.00 GB Shell: version: 3.2.57 path: /bin/bash Binaries: Node: version: 18.18.2 path: /opt/homebrew/opt/node@18/bin/node Yarn: Not Found npm: version: 7.24.2 path: ???/node_modules/.bin/npm Watchman: version: 2023.07.10.00 path: /opt/homebrew/bin/watchman Managers: CocoaPods: version: 1.12.1 path: /opt/homebrew/bin/pod SDKs: iOS SDK: Platforms:

Steps to reproduce

  1. Build a debug apk with index.android.bundle
  2. Side load the apk to a Samsung S20 Ultra or Pixel 5
  3. Start the app
  4. Observe if an error screen shows up
  5. If no, delete the app and repeat step 2 to 4

Snack, screenshot, or link to a repository

Screenshot 2023-11-16 at 2 35 39 PM

github-actions[bot] commented 11 months ago
:warning: Newer Version of React Native is Available!
:information_source: You are on a supported minor version, but it looks like there's a newer patch available - 0.72.7. Please upgrade to the highest patch for your minor or latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If it does not repro, please let us know so we can close out this issue. This helps us ensure we are looking at issues that still exist in the most recent releases.
github-actions[bot] commented 11 months ago
:warning: Missing Reproducible Example
:information_source: We could not detect a reproducible example in your issue report. Please provide either:
  • If your bug is UI related: a Snack
  • If your bug is build/update related: use our Reproducer Template. A reproducer needs to be in a GitHub repository under your username.
cortinico commented 11 months ago

Side load the apk to a Samsung S20 Ultra or Pixel 5

Is this specific to those 2 modules only?

tomgreco commented 11 months ago

I'm having the same issue on pixel 6a emulator

simonxcheng commented 11 months ago

Side load the apk to a Samsung S20 Ultra or Pixel 5

Is this specific to those 2 modules only?

We found the same issue on pixel 6 pro, pixel 2 xl, pixel 4a, and Samsung s21

mrcnserkan commented 9 months ago

I'm having the same problem, and I don't think it is a device issue. Did you find any solutions?

simonxcheng commented 9 months ago

Side load the apk to a Samsung S20 Ultra or Pixel 5

Is this specific to those 2 modules only?

@cortinico We released our production build with RN 0.72.8. We found that 5% users got this crash with phased rollout. We also found a workaround to reduce the crash percentage to 0.15%. However, it is still too hight for us because our crash-free rate was about 99.95%. I guess the issue is coming from some race conditions could not being handled with RN Framework. It did not happen with RN 0.68. Here are some details. I can not reveal our source code. I am not sure if it necessary to create a separate project to reproduce this issue without my company's source code. Please let me know if you need more information.

  1. Our code is sophisticated. We are using brown field approach to build our app. The home page has a native ViewPager to render 5 tabs. The 1st, 2nd, and 4th are rendered by RN fragements, the 3rd and 5th are rendered in native fragments. We used to set offscreenPageLimit to 4 to keep these fragments in memory. There is no issues with RN 0.68 and ealier versions.
image002
  1. The crash only happens 1~2 seconds after the app starts
  2. The work around we found is to delay loading other fragments. So, when the activity being created, we set offscreenPageLimit to 1. After 100ms, we set offscreenPageLimit to 4. With this tweak, we elimiated 97% crashes.
  3. In debug mode and by setting break points on UIImplementation.java. I did not find registerRootView has been invoked for node 21. How node tag is generated? In RN souce code, allocateTag never generate a tag with value 21.

Callstack: Caused by java.lang.AssertionError: Root node with tag 21 doesn't exist

   at com.facebook.infer.annotation.Assertions.assertNotNull(Assertions.java:19)

   at com.facebook.react.uimanager.UIImplementation.createView(UIImplementation.java:235)

   at com.facebook.react.uimanager.UIManagerModule.createView(UIManagerModule.java:419)

   at java.lang.reflect.Method.invoke(Method.java)

   at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:372)

   at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:188)

   at com.facebook.jni.NativeRunnable.run(NativeRunnable.java)

   at android.os.Handler.handleCallback(Handler.java:958)

   at android.os.Handler.dispatchMessage(Handler.java:99)

   at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:27)

   at android.os.Looper.loopOnce(Looper.java:230)

   at android.os.Looper.loop(Looper.java:319)

   at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:228)

   at java.lang.Thread.run(Thread.java:1012)
cortinico commented 9 months ago

Please let me know if you need more information.

We would need a reproducer as suggested by the bot

tgreco commented 8 months ago
import static android.content.Context.VIBRATOR_SERVICE;

import android.app.Activity;
import android.app.PendingIntent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.os.Build;
import android.os.Bundle;
import android.os.VibrationEffect;
import android.os.Vibrator;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.github.devnied.emvnfccard.enums.EmvCardScheme;
import com.github.devnied.emvnfccard.model.Application;
import com.github.devnied.emvnfccard.model.EmvCard;
import com.github.devnied.emvnfccard.parser.EmvTemplate;

import java.io.IOException;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;

public class NativeModule extends ReactContextBaseJavaModule implements NfcAdapter.ReaderCallback {
    /**
     * Name of component so it can be accessed in JS
     */
    public static final String REACT_CLASS = "NativeModule";
    private Activity activity;
    private NfcAdapter mNfcAdapter;
    private Promise readNfcPromise;

    NativeModule(ReactApplicationContext context) {
        super(context);
        activity = context.getCurrentActivity();

        // For NFC reading
        mNfcAdapter = NfcAdapter.getDefaultAdapter(context);
    }

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

    @ReactMethod
    public void readNfc(Promise promise) {
        if(promise != null) {
            if(readNfcPromise != null) {
                promise.reject("Already reading", "Already reading");
                return;
            }
            if (mNfcAdapter != null) {
                this.readNfcPromise = promise;
                Bundle options = new Bundle();
                // Work around for some broken Nfc firmware implementations that poll the card too fast
                options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250);

                // Enable ReaderMode for all types of card and disable platform sounds
                // the option NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK is NOT set
                // to get the data of the tag after reading
                mNfcAdapter.enableReaderMode(this.activity,
                        this,
                        NfcAdapter.FLAG_READER_NFC_A |
                                NfcAdapter.FLAG_READER_NFC_B |
                                NfcAdapter.FLAG_READER_NFC_F |
                                NfcAdapter.FLAG_READER_NFC_V |
                                NfcAdapter.FLAG_READER_NFC_BARCODE |
                                NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS,
                        options);
            } else {
                promise.reject("NFC reader not initialized", "NFC reader not initialized");
                return;
            }
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public void onTagDiscovered(Tag tag) {
    }
}

I recently added NFC reading to my native module and I am getting this error now. It's related to this line mNfcAdapter = NfcAdapter.getDefaultAdapter(context);

If I comment that line out I don't get the error, but then nothing works because I need that line :)

simonxcheng commented 8 months ago

Please let me know if you need more information.

We would need a reproducer as suggested by the bot

I believe I found out why our app is carshing. We use the following code in our RN Fragment to update props when there are some data changes. If a such change occurs during the initialization of the RN App inside the RN Fragment, the error happens:

reactRootView?.appProperties = newProps

The only fix we can apply right now is to deplay the appProperties update. We will try to use DeviceEventEmitter to update data from Native to RN to see if we can remove the issue completely.

simonxcheng commented 8 months ago
import static android.content.Context.VIBRATOR_SERVICE;

import android.app.Activity;
import android.app.PendingIntent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.os.Build;
import android.os.Bundle;
import android.os.VibrationEffect;
import android.os.Vibrator;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.github.devnied.emvnfccard.enums.EmvCardScheme;
import com.github.devnied.emvnfccard.model.Application;
import com.github.devnied.emvnfccard.model.EmvCard;
import com.github.devnied.emvnfccard.parser.EmvTemplate;

import java.io.IOException;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;

public class NativeModule extends ReactContextBaseJavaModule implements NfcAdapter.ReaderCallback {
    /**
     * Name of component so it can be accessed in JS
     */
    public static final String REACT_CLASS = "NativeModule";
    private Activity activity;
    private NfcAdapter mNfcAdapter;
    private Promise readNfcPromise;

    NativeModule(ReactApplicationContext context) {
        super(context);
        activity = context.getCurrentActivity();

        // For NFC reading
        mNfcAdapter = NfcAdapter.getDefaultAdapter(context);
    }

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

    @ReactMethod
    public void readNfc(Promise promise) {
        if(promise != null) {
            if(readNfcPromise != null) {
                promise.reject("Already reading", "Already reading");
                return;
            }
            if (mNfcAdapter != null) {
                this.readNfcPromise = promise;
                Bundle options = new Bundle();
                // Work around for some broken Nfc firmware implementations that poll the card too fast
                options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250);

                // Enable ReaderMode for all types of card and disable platform sounds
                // the option NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK is NOT set
                // to get the data of the tag after reading
                mNfcAdapter.enableReaderMode(this.activity,
                        this,
                        NfcAdapter.FLAG_READER_NFC_A |
                                NfcAdapter.FLAG_READER_NFC_B |
                                NfcAdapter.FLAG_READER_NFC_F |
                                NfcAdapter.FLAG_READER_NFC_V |
                                NfcAdapter.FLAG_READER_NFC_BARCODE |
                                NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS,
                        options);
            } else {
                promise.reject("NFC reader not initialized", "NFC reader not initialized");
                return;
            }
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public void onTagDiscovered(Tag tag) {
    }
}

I recently added NFC reading to my native module and I am getting this error now. It's related to this line mNfcAdapter = NfcAdapter.getDefaultAdapter(context);

If I comment that line out I don't get the error, but then nothing works because I need that line :) @tgreco Does NfcAdapter trigger any props update of your RN components? From my experience, props change causes this issue if the component is still loading/initialzing

tgreco commented 8 months ago

The way I solved this was by moving mNfcAdapter = NfcAdapter.getDefaultAdapter(context); into a method that I would call from react-native along with using getReactApplicationContext() instead of the context that was passed in the constructor.

Now it is working fine.

react-native-bot commented 2 months ago

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.

react-native-bot commented 2 months ago

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.

react-native-bot commented 2 months ago

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

Sourav-sps commented 1 month ago

+1