zoontek / react-native-bootsplash

πŸš€ Show a splash screen during app startup. Hide it when you are ready.
MIT License
3.73k stars 256 forks source link

Couldn't call show() without a valid Activity #187

Closed birdofpreyru closed 3 years ago

birdofpreyru commented 3 years ago

Bug report

Summary

Occasionally (but rarely) I get this error due to react-native-bootsplash, causing the app to hang:

Error: Couldn't call show() without a valid Activity
  at anonymous(.../node_modules/react-native/Libraries/BatchedBridge/NativeModules.js:103:promiseMethodWrapper)
  at o(.../node_modules/react-native-bootsplash/dist/commonjs/index.js:23:hide)

I am pretty sure that I have react-native-bootslpash configured as per instructions, as most of the times its works for me as intended (and when the problem happens, killing & restarting the app usually helps to get it loaded).

I suspect you might have some race condition in the code, leading to crash when in some rare cases the RN code is executed and RNBootslash .hide() is called before the bootslash is correctly loaded.

Environment info

react-native info output:

System:
    OS: Linux 5.4 Ubuntu 20.04.1 LTS (Focal Fossa)
    CPU: (8) x64 Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
    Memory: 973.41 MB / 11.60 GB
    Shell: 5.0.17 - /bin/bash
  Binaries:
    Node: 14.15.1 - ~/.nvm/versions/node/v14.15.1/bin/node
    Yarn: Not Found
    npm: 6.14.8 - ~/.nvm/versions/node/v14.15.1/bin/npm
    Watchman: Not Found
  SDKs:
    Android SDK: Not Found
  IDEs:
    Android Studio: Not Found
  Languages:
    Java: 11.0.9.1 - /usr/bin/javac
    Python: Not Found
  npmPackages:
    @react-native-community/cli: Not Found
    react: ^17.0.1 => 17.0.1 
    react-native: ^0.63.3 => 0.63.3 
  npmGlobalPackages:
    *react-native*: Not Found

Library version: 3.1.1

Steps to reproduce

I don't have a realiable way to reproduce. The bug manifests itself randomly from time to time. In the nutshell I don't do anything fancy: I have the bootslash installed as per instructions, when my React Native app starts it rapidly does some async initializations, and once they are ready it calls RNBootSplash.hide(..).

zoontek commented 3 years ago

This currently happen when the current activity is null or is finishing: https://github.com/zoontek/react-native-bootsplash/blob/master/android/src/main/java/com/zoontek/rnbootsplash/RNBootSplashModule.java#L229

It might be related to the SplashActivity. If getCurrentActivity could return an instance of it instead of MainActivity, the promise could be rejected.

I will check. Did this happen before, or thee issue is recent?

birdofpreyru commented 3 years ago

I believe, it happens for at least few months (but only now I arrived to actually catch the trace and determined it is caused by the bootslpash).

zoontek commented 3 years ago

At which frequency? On first load or on reload? I would love to reproduce this to debug it, but never encounter it myself.

birdofpreyru commented 3 years ago

Rarely, and it seems to mostly happen when a freshly installed app is started for the first time. Probably, it has something to do with interference to other libraries I use. In particular, I use @react-native-firebase/admob, which on the very first run of the app creates an ads consent form (and on subsequent runs it does not create it, as it already remembers the consent result). I imagine, it might create its own activity when doing it, and it may clash with what bootsplash does?

zoontek commented 3 years ago

I don't think it could clash. I looked at the ReactContext source code, getCurrentActivity always return an instance of ReactActivity (MainActivity).

I found an 100% similar check in the Logbox module: https://github.com/facebook/react-native/blob/0.64-stable/ReactAndroid/src/main/java/com/facebook/react/devsupport/LogBoxModule.java#L63

The error is "Unable to launch logbox because react activity is not available". But I doesn't explain in which cases this could happen.

birdofpreyru commented 3 years ago

I know nothing about it, but I guess in their cases some external logic may safeguard that code with check is not executed too earlier, before the current activity is available?

zoontek commented 3 years ago

@birdofpreyru I replaced the Promise rejection with a "Wait a bit and try to rerun" (as the task are paused when the app is in background, it will wait for the Activity creation / re-creation). Could you try it and confirm that everything is OK?

https://github.com/zoontek/react-native-bootsplash/releases/tag/3.1.2

birdofpreyru commented 3 years ago

@zoontek not quite, now when the issue happens, there is no error, but the splashscreen does not hide either, the .hide() call is just silently ignored.

In the meantime I found a way to reproduce it: if I deploy the app to device with short screen lock time, and by the moment the deployment is finished the screen is already locked, when I unlock the screen - the app is already open, and this problem is there. Also found a workaround: if instead calling .hide() when the app seems ready I just schedule the call 1 second after the root component rendering, it seems to work fine.

Probably you need to add some sort of async barrier: so that when .hide() is called it actually waits for a flag saying that the splash screen have been shown up already?

lulmichal commented 3 years ago

Hello everyone. I experience the same issue. When the screen is locked and the build (no matter if debug or release) is finished and I unlock the screen I see bootsplash. Then when I kill the app and open it again everything works as expected. It's only happening after installation. To be precise it's also happening for anyone who will download the app from Google Play "Internal testing".

I did what @birdofpreyru said which is adding setTimeout in root component (App.js) render to execute .hide() after one second and now it works.

I'm sorry but I'm new to programming therefore if my help is required to somehow reproduce the issue or anything - you'll need to be the most direct as possible :)

zoontek commented 3 years ago

@birdofpreyru @lulmichal Could you try the latest version? https://github.com/zoontek/react-native-bootsplash/releases/tag/3.2.3

lulmichal commented 3 years ago

@zoontek I'm sorry, I can't find 10 minutes to check it. As soon as I find the time I'll reply. Thank you.

lulmichal commented 3 years ago

@zoontek Hello, I'm sorry it took so long. Unfortunately the issue remains. How else can I help?

lulmichal commented 3 years ago

Screenshot 2021-05-14 at 12 16 57

After downloading from store or after installing by npx react-native run-android my Splash.js component just freeze after invoking hide(). There is no "STATUS AFTER" in the console. After killing the app and reopen I can see second console.log. I hope maybe that can help. If not just let me know what else do you need. Thanks!

lulmichal commented 3 years ago

@zoontek @birdofpreyru I received a feedback from user who updated their app version and even I implemented @birdofpreyru solution and it works for me, she still encountered this problem. The thing is I set timeout for 500ms and not 1s. Do you think this might cause it? Can changing the timeout value for longer help? Thank you.

birdofpreyru commented 3 years ago

Hey @lulmichal , @zoontek , I had no chance to try the latest lib version, but it looks the issue is still in there anyway. Regarding "my solution", in case it might help, I do literally this in my app's root component:

useEffect(() => {
  const id = setTimeout(async () => {
    // coreReady is a promise for some internal intiialization, which I need to be completed
    // before I am ready to render the actual content instead of the bootslash.
    await coreReady;
    RNBootSplash.hide({fade: true});
  }, 1000);
  return () => {
    clearTimeout(id);
  };
}, []);

so, I don't wait for .hide(..) I just fire it. Don't remember now, why I choose 1 sec. delay, and whether smaller values did not work for me.

zoontek commented 3 years ago

@lulmichal @birdofpreyru I'm again trying to reproduce the issue, without success πŸ˜• I uninstall the app, build it with the lock screen on, and when I unlock the phone, the splash screen disappear.

Could you share your MainActivity.java file? Or a repository with the smallest amount of code to reproduce the issue so I can investigate?

birdofpreyru commented 3 years ago

@zoontek coming up with a minimal code reproducing the issue may take me some time. Regarding JAVA files in my project, MainActivity.java is just trivial:

// MainActivity.java

package com.domain.subdomain;

import android.os.Bundle;
import com.facebook.react.ReactActivity;
import com.zoontek.rnbootsplash.RNBootSplash;

public class MainActivity extends ReactActivity {

  @Override
  protected String getMainComponentName() {
    return "APP NAME";
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    RNBootSplash.init(R.drawable.bootsplash, MainActivity.this);
  }
}

and my manifest:

// AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.domain.subdomain">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="com.android.vending.BILLING" />

    <application
      android:name=".MainApplication"
      android:label="@string/app_name"
      android:icon="@mipmap/ic_launcher"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:allowBackup="false"
      android:theme="@style/AppTheme"
      android:usesCleartextTraffic="true">
      <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
        android:launchMode="singleTask"
        android:windowSoftInputMode="adjustResize"
        android:exported="true">
      </activity>
      <activity
        android:name="com.zoontek.rnbootsplash.RNBootSplashActivity"
        android:theme="@style/BootTheme">
        <intent-filter>
          <action android:name="android.intent.action.MAIN" />
          <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
      </activity>
    </application>
</manifest>
lulmichal commented 3 years ago

Good Morning @zoontek

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.random.random">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <uses-permission android:name="com.android.vending.BILLING" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <!-- < Only if you're using GCM or localNotificationSchedule() > -->
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <permission
        android:name="${applicationId}.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />
    <uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
    <!-- < Only if you're using GCM or localNotificationSchedule() > -->

    <application
      android:name=".MainApplication"
      android:label="@string/app_name"
      android:icon="@mipmap/ic_launcher"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:allowBackup="false"
      android:theme="@style/AppTheme"
    >

      <meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id"/>
      <meta-data  android:name="com.dieam.reactnativepushnotification.notification_channel_name"
              android:value="Random"/>
      <meta-data  android:name="com.dieam.reactnativepushnotification.notification_channel_description"
                  android:value="Gentle Sleep Reminder"/>
      <meta-data  android:name="com.dieam.reactnativepushnotification.notification_color"
                  android:resource="@color/white"/>

      <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
      <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver">
          <intent-filter>
              <action android:name="android.intent.action.BOOT_COMPLETED" />
          </intent-filter>
      </receiver>
      <service android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationRegistrationService"/>

      <service
        android:name=".PushNotificationService"
        android:enabled="true"
        android:exported="true">
          <intent-filter>
            <action android:name="com.google.firebase.MESSAGING_EVENT" />
          </intent-filter>
      </service>
      <service android:name=".MainInstanceIdService" android:exported="false">
        <intent-filter>
          <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
        </intent-filter>
      </service>
      <service android:name="io.invertase.firebase.messaging.RNFirebaseBackgroundMessagingService" />

      <activity
        android:name=".MainActivity"
        android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
        android:label="@string/app_name"
        android:windowSoftInputMode="adjustResize"
        android:exported="true"
        android:launchMode="singleTask">
      </activity>
      <activity
        android:name="com.zoontek.rnbootsplash.RNBootSplashActivity"
        android:theme="@style/BootTheme"
        android:launchMode="singleTask">
        <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
      </activity>
      <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
    </application>

</manifest>
package com.random.random;

import com.facebook.react.ReactActivity;

import com.zoontek.rnbootsplash.RNBootSplash;

import android.os.Bundle;

public class MainActivity extends ReactActivity {

  /**
   * Returns the name of the main component registered from JavaScript. This is used to schedule
   * rendering of the component.
   */
  @Override
  protected String getMainComponentName() {
    return "Random";
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    RNBootSplash.init(R.drawable.bootsplash, MainActivity.this); 
  }
}

Just so you know, the problem also occurs on iOS. Most of my clients users experience the issue on iOS when they update the app.

I noticed one thing. I "build & run" app in my Xcode. My display turned off after 15 minutes of no usage. When I came back there was the BootSplash screen. Killing the app didn't help, "build & run" didn't help. Only after I erased all content and setting from virtual device and then "build & run" it worked. Maybe that can help you.

Thank you for your work. If you need anything please let me know. When I find some time I will prepare the repo.

Best regards.

lulmichal commented 3 years ago

Another similar experience - I finished my work, I closed Metro, kill the App, stop the run in Xcode and DIDN'T clear the device. Next day I just "build & run" and same issue.

zoontek commented 3 years ago

The new version implements AndroidX splash screen module and does not need a new Activity anymore, so this shouldn't be an issue anymore (and the show method has been removed)