zoontek / react-native-bootsplash

🚀 Show a splash screen during app startup. Hide it when you are ready.
MIT License
3.75k stars 259 forks source link

Runtime crash: You need to use a Theme.AppCompat theme (or descendant) with this activity #379

Closed kylecesmat closed 2 years ago

kylecesmat commented 2 years ago

Bug summary

Hello, we are seeing <1% of our Android users experiencing this crash. I've read through the various threads & have validated configuration as recommended by the README & in the splashscreen-sample.

I am unable to create a reproduction, but have a theory that the RNBootsplash.init call may be exiting early, before the postSplashScreenTheme API can be called. Again, we cannot reproduce locally or see any platform/API version trends in our runtime error trace.

As a possible solution, I wonder if there is a way to force postSplashScreenTheme to be called, in the event that the RNBootSplash.init fails for some reason or exits early, so that the rest of the application can properly fall back to Theme.AppCompat.Light.NoActionBar in the top-level root theme?

Observing the following error in a small percentage of our logs:

Fatal Exception: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
       at androidx.appcompat.app.AppCompatDelegateImpl.createSubDecor(AppCompatDelegateImpl.java:846)
       at androidx.appcompat.app.AppCompatDelegateImpl.ensureSubDecor(AppCompatDelegateImpl.java:809)
       at androidx.appcompat.app.AppCompatDelegateImpl.onPostCreate(AppCompatDelegateImpl.java:530)
       at androidx.appcompat.app.AppCompatActivity.onPostCreate(AppCompatActivity.java:151)
       at android.app.Instrumentation.callActivityOnPostCreate(Instrumentation.java:1380)
       at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3365)

Interestingly we also see this error as well:

Fatal Exception: java.lang.NullPointerException: Attempt to invoke direct method 'void android.view.SurfaceControl.checkNotReleased()' on a null object reference
       at android.view.SurfaceControl.access$2600(SurfaceControl.java:89)
       at android.view.SurfaceControl$Transaction.checkPreconditions(SurfaceControl.java:2693)
       at android.view.SurfaceControl$Transaction.hide(SurfaceControl.java:2841)
       at android.app.ActivityThread.syncTransferSplashscreenViewTransaction(ActivityThread.java:4198)
       at android.app.ActivityThread.access$4500(ActivityThread.java:256)
       at android.app.ActivityThread$1.onDraw(ActivityThread.java:4175)
       at android.view.ViewTreeObserver.dispatchOnDraw(ViewTreeObserver.java:1132)
       at android.view.ViewRootImpl.draw(ViewRootImpl.java:4462)
       at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4251)

And here are the relevant lines in our configuration:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="BaseAppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
        <item name="android:statusBarColor">@android:color/transparent</item>
        <item name="android:windowBackground">@color/blue</item>
        <item name="android:colorPrimary">@color/blue</item>
        <item name="android:navigationBarColor">@color/blue</item>
        <item name="android:windowLightStatusBar">false</item>
        <!-- Caret color -->
        <item name="android:colorControlActivated">@color/blue</item>
        <!-- Highlight color -->
        <item name="android:textColorHighlight">@color/blueWash</item>
    </style>
    <style name="AppTheme" parent="BaseAppTheme" />
    <style name="BootTheme" parent="Theme.SplashScreen">
        <item name="windowSplashScreenBackground">@color/blue</item>
        <item name="windowSplashScreenAnimatedIcon">@mipmap/bootsplash_logo</item>
        <item name="postSplashScreenTheme">@style/AppTheme</item>
    </style>
</resources>
protected ReactActivityDelegate createReactActivityDelegate() {
    return new ReactActivityDelegate(this, getMainComponentName()) {
      @Override
      protected Bundle getLaunchOptions() {
        Bundle initialProperties = new Bundle();
        initialProperties.putBoolean("appWasRestored", hasSavedInstanceState);
        return initialProperties;
      }

      @Override
      protected ReactRootView createRootView() {
        ReactRootView reactRootView = new ReactRootView(getContext());
        reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED);
        return reactRootView;
      }

      @Override
      protected void loadApp(String appKey) {
        RNBootSplash.init(getPlainActivity());
        super.loadApp(appKey);
      }
    };
  }
buildscript {
    ext {
        buildToolsVersion = "31.0.0"
        minSdkVersion = 23
        compileSdkVersion = 31
        targetSdkVersion = 31
dependencies {
    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
    implementation "androidx.core:core-splashscreen:1.0.0-rc01"
<application
        // ...other configuration
        android:name=".MainApplication"
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/BootTheme">

Library version

4.2.4

Environment info

SDKs:
    iOS SDK:
      Platforms: DriverKit 21.4, iOS 15.5, macOS 12.3, tvOS 15.4, watchOS 8.5
    Android SDK:
      API Levels: 28, 29, 30, 31, 32
      Build Tools: 29.0.2, 29.0.3, 30.0.2, 30.0.3, 31.0.0, 33.0.0
      System Images: android-30 | Google APIs Intel x86 Atom, android-30 | Google Play Intel x86 Atom
      Android NDK: Not Found
  IDEs:
    Android Studio: 2021.1 AI-211.7628.21.2111.8309675
    Xcode: 13.4.1/13F100 - /usr/bin/xcodebuild
  Languages:
    Java: 11.0.15 - /usr/bin/javac

Steps to reproduce

Only clear way to reproduce is to comment-out RNBootSplash.init(getPlainActivity());. I believe this simulates the failure case our users see where the module does not resolve correctly and postSplashScreenTheme is never reached, meaning the AppCompat theme is never set.

Reproducible sample code

Unable to reproduce locally, only observed in runtime errors.
zoontek commented 2 years ago

Hi @kylecesmat 👋 Do you have any metric about these users (mainly to know if they use Android >= 12)?

As you can see here, init is a simple static method calling installSplashScreen. The only check performed is that the activity is not null (which will be just above in your logs if it happens), so I guess that's not it.

I'm thinking it could be a race condition: MainActivityDelegate.loadApp might be called too late. Moving it to onCreate will call it sooner (when MainActivity is created).

The splashscreen-sample calls installSplashScreen after super call, which result in a crash (You need to use a Theme.AppCompat theme (or descendant) with this activity). The official guide put it before the super call, so you can try init it like this (tried it, it works)

PS: I would be really glad if you can provides feedbacks about this change, as it's not a big deal for the library (a small README change) 😄

+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+   RNBootSplash.init(this);
+   super.onCreate(null);
+ }

protected ReactActivityDelegate createReactActivityDelegate() {
    return new ReactActivityDelegate(this, getMainComponentName()) {
      @Override
      protected Bundle getLaunchOptions() {
        Bundle initialProperties = new Bundle();
        initialProperties.putBoolean("appWasRestored", hasSavedInstanceState);
        return initialProperties;
      }

      @Override
      protected ReactRootView createRootView() {
        ReactRootView reactRootView = new ReactRootView(getContext());
        reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED);
        return reactRootView;
      }

-     @Override
-     protected void loadApp(String appKey) {
-       RNBootSplash.init(getPlainActivity());
-       super.loadApp(appKey);
-     }
    };
  }
kylecesmat commented 2 years ago

Thank you so much for the helpful suggestions! Let me work on integrating & testing this change - And will absolutely let you know the results.

The crashes seem to all be from Android 12, with ~10% of the logs from Android 10.

zoontek commented 2 years ago

@kylecesmat On Android 12, the native SplashScreen API starts on the Android system thread (as opposed as the compat implem for the versions before), so it seems normal: it's even quicker to boot. That fits with the previous theory 🙂

kylecesmat commented 2 years ago

hey @zoontek the fix you suggested works great! We no longer see this crash in runtime logs.

That said, there is still a crash we are seeing that I believe is a Google issue. It's the second crash I posted above, but I outlined it a bit more in this crash report here: https://issuetracker.google.com/issues/242118185

Seems others have hit this issue as well: https://issuetracker.google.com/issues/233588959

Anyway, feel free to close this issue as the configuration issue is now fixed :) Thank you!

zoontek commented 2 years ago

@kylecesmat Indeed, this seems not tied to the library itself. I'm closing this.

Since I saw you work at Coinbase, a gentle reminder to consider sponsoring. I think you are also using react-native-localize and react-native-permissions, which I also maintain 🙂!

kylecesmat commented 2 years ago

@zoontek I'm doing my best internally to push for an OSS fund to support projects like this. Will absolutely keep you informed if we make progress - I understand that's not the best response 😓

Thank you again for your time & OSS contributions!