OneSignal / OneSignal-Android-SDK

OneSignal is a free push notification service for mobile apps. This plugin makes it easy to integrate your native Android or Amazon app with OneSignal. https://onesignal.com
Other
605 stars 369 forks source link

[Bug]: Crashes when OneSignal is used immediately after initiWithContext called #2192

Open fanwgwg opened 2 months ago

fanwgwg commented 2 months ago

What happened?

In my code, I've consistently encountered crashes when using OneSignal APIs after calling OneSignal.initWithContext, specifically receiving the error: java.lang.Exception: Must call 'initWithContext' before use.

I believe this is related to the recent changes in PR #2151, which moved initialization to a background thread. However, there seems to be no clear guidance from OneSignal on how to determine when the SDK has completed initialization and when it's safe to call other APIs.

Relying on developers to wait for an arbitrary period, such as 2 seconds, before invoking OneSignal APIs is not a viable or professional coding practice—this is not something we see in other SDKs. If the initialization is now asynchronous, then the rest of the APIs should also be asynchronous. For instance, APIs could return a result only after initialization has completed.

Overall, I feel the quality of the SDK urgently needs improvement, especially given the frequent crashes introduced since the 5.0 release. Addressing these issues with more robust handling of the initialization process is crucial.

Steps to reproduce?

OneSignal.initWithContext(xxx);
OneSignal.getUser();

What did you expect to happen?

The code should not crash

OneSignal Android SDK version

com.onesignal:OneSignal:5.1.21

Android version

14

Specific Android models

No response

Relevant log output

No response

Code of Conduct

jinliu9508 commented 1 month ago

Hello @fanwgwg, thank you for the report. Could you share more of the stack trace regarding the crash? I have tested your code but was unable to reproduce the exception. Part of the initialization has been moved to the background; however, you can still successfully call methods like getUser() immediately after initWithContext(). The system allows components like User to be initialized even if the background initialization is not yet complete. In this case, the system will simply use the component that was initialized by your call.

I do notice that this error can occur in certain multi-threading environments that are not specifically covered in our documentation. If you could provide more details, including how you are calling OneSignal API methods and the stack trace when the crash occurs, I can assist in identifying the root cause of the issue.

fanwgwg commented 1 month ago

Here's the stacktrace:

 Caused by java.lang.Exception: Must call 'initWithContext' before use
       at com.onesignal.internal.OneSignalImp.getUser(OneSignalImp.kt:123)
       at com.onesignal.OneSignal.getUser(OneSignal.kt:46)
       at com.xxxxx.appsettings.AppSettingsViewModel.refreshNotificationStatus(AppSettingsViewModel.java:82)
       at com.xxxxx.appsettings.AppSettingsViewModel.init(AppSettingsViewModel.java:191)
       at com.xxxxx.appsettings.AppSettingsViewModel.<init>(AppSettingsViewModel.java:61)
       at com.xxxxx.appsettings.AppSettingsViewModel_Factory.newInstance(AppSettingsViewModel_Factory.java:83)
       at com.xxxxx.DaggerNoteApplication_HiltComponents_SingletonC$ViewModelCImpl$SwitchingProvider.get(DaggerNoteApplication_HiltComponents_SingletonC.java:3154)
       at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory$2.createViewModel(HiltViewModelFactory.java:132)
       at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory$2.create(HiltViewModelFactory.java:103)
       at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:170)
       at androidx.lifecycle.ViewModelProvider$Factory.create(ViewModelProvider.android.kt:158)
       at androidx.lifecycle.viewmodel.ViewModelProviderImpl_androidKt.createViewModel(ViewModelProviderImpl_android.kt:34)
       at androidx.lifecycle.viewmodel.ViewModelProviderImpl.getViewModel$lifecycle_viewmodel_release(ViewModelProviderImpl.java:65)
       at androidx.lifecycle.viewmodel.ViewModelProviderImpl.getViewModel$lifecycle_viewmodel_release$default(ViewModelProviderImpl.java:47)
       at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:91)
       at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:109)

The code setup is that: AppSettingsActivity

@AndroidEntryPoint
public class AppSettingsActivity extends AppCompatActivity {

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    OneSignal.initWithContext(context, CredentialUtils.getOneSignalId());
    AppSettingsViewModel appSettingsViewModel =
        new ViewModelProvider(this).get(AppSettingsViewModel.class);
   }
}

AppSettingsViewModel

  @Inject
  AppSettingsViewModel() {
     init()
  }

  private void init() {
     refreshNotificationStatus();
  }

  private void refreshNotificationStatus() {
     boolean isOptIn = OneSignal.getUser().getPushSubscription().getOptedIn();
  }
jinliu9508 commented 1 month ago

@fanwgwg Are you still able to reproduce the exception if you call 'OneSignal.getUser().getPushSubscription().getOptedIn();' immediately after 'OneSignal.initWithContext()'? It seems that your ViewModelProvider might be initialized earlier than AppSettingsActivity.onCreate(), depending on how your injection works.

Does this exception occur every time, most of the time, or only occasionally when you run the app?

fanwgwg commented 1 month ago

I'm not able to reproduce it, I'm only seeing it from Crashlytics. Based on the stacktrace, I do believe that AppSettingsActivity.onCreate() is already called before OneSignal.getUser() is called.

Sharing the complete stacktrace here as I only pasted the ones related to the viewmodel in my previous reply. This is the exact stacktrace on Crashlytics, except that I've removed our package id.

You'll a line of com.xxxxx.appsettings.AppSettingsActivity.onCreate (AppSettingsActivity.java) inside the stacktrace.

com.onesignal.internal.OneSignalImp.getUser (OneSignalImp.kt:123)
com.onesignal.OneSignal.getUser (OneSignal.kt:46)
com.xxxxx.appsettings.AppSettingsViewModel.refreshNotificationStatus (AppSettingsViewModel.java:82)
com.xxxxx.appsettings.AppSettingsViewModel.init (AppSettingsViewModel.java:191)
com.xxxxx.appsettings.AppSettingsViewModel.<init> (AppSettingsViewModel.java:61)
com.xxxxx.appsettings.AppSettingsViewModel_Factory.newInstance (AppSettingsViewModel_Factory.java:83)
com.xxxxx.DaggerNoteApplication_HiltComponents_SingletonC$ViewModelCImpl$SwitchingProvider.get (DaggerNoteApplication_HiltComponents_SingletonC.java:3154)
dagger.hilt.android.internal.lifecycle.HiltViewModelFactory$2.createViewModel (HiltViewModelFactory.java:132)
dagger.hilt.android.internal.lifecycle.HiltViewModelFactory$2.create (HiltViewModelFactory.java:103)
dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.create (HiltViewModelFactory.java:170)
androidx.lifecycle.ViewModelProvider$Factory.create (ViewModelProvider.android.kt:158)
androidx.lifecycle.viewmodel.ViewModelProviderImpl_androidKt.createViewModel (ViewModelProviderImpl_android.kt:34)
androidx.lifecycle.viewmodel.ViewModelProviderImpl.getViewModel$lifecycle_viewmodel_release (ViewModelProviderImpl.java:65)
androidx.lifecycle.viewmodel.ViewModelProviderImpl.getViewModel$lifecycle_viewmodel_release$default (ViewModelProviderImpl.java:47)
androidx.lifecycle.ViewModelProvider.get (ViewModelProvider.java:91)
androidx.lifecycle.ViewModelProvider.get (ViewModelProvider.java:109)
com.xxxxx.appsettings.AppSettingsFragment.onCreate (AppSettingsFragment.java:58)
androidx.fragment.app.Fragment.performCreate (Fragment.java:3099)
androidx.fragment.app.FragmentStateManager.create (FragmentStateManager.java:524)
androidx.fragment.app.FragmentStateManager.moveToExpectedState (FragmentStateManager.java:282)
androidx.fragment.app.FragmentStore.moveToExpectedState (FragmentStore.java:114)
androidx.fragment.app.FragmentManager.moveToState (FragmentManager.java:1675)
androidx.fragment.app.FragmentManager.dispatchStateChange (FragmentManager.java:3269)
androidx.fragment.app.FragmentManager.dispatchCreate (FragmentManager.java:3176)
androidx.fragment.app.Fragment.restoreChildFragmentState (Fragment.java:1994)
androidx.fragment.app.Fragment.onCreate (Fragment.java:1972)
androidx.navigation.fragment.NavHostFragment.onCreate (NavHostFragment.kt:163)
androidx.fragment.app.Fragment.performCreate (Fragment.java:3099)
androidx.fragment.app.FragmentStateManager.create (FragmentStateManager.java:524)
androidx.fragment.app.FragmentStateManager.moveToExpectedState (FragmentStateManager.java:282)
androidx.fragment.app.FragmentStore.moveToExpectedState (FragmentStore.java:114)
androidx.fragment.app.FragmentManager.moveToState (FragmentManager.java:1675)
androidx.fragment.app.FragmentManager.dispatchStateChange (FragmentManager.java:3269)
androidx.fragment.app.FragmentManager.dispatchCreate (FragmentManager.java:3176)
androidx.fragment.app.FragmentController.dispatchCreate (FragmentController.java:252)
androidx.fragment.app.FragmentActivity.onCreate (FragmentActivity.java:219)
com.xxxxx.appsettings.Hilt_AppSettingsActivity.onCreate (Hilt_AppSettingsActivity.java)
com.xxxxx.appsettings.AppSettingsActivity.onCreate (AppSettingsActivity.java)
android.app.Activity.performCreate (Activity.java:8657)
android.app.Activity.performCreate (Activity.java:8636)
android.app.Instrumentation.callActivityOnCreate (Instrumentation.java:1417)
android.app.ActivityThread.performLaunchActivity (ActivityThread.java:4165)
android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:4340)
android.app.servertransaction.LaunchActivityItem.execute (LaunchActivityItem.java:101)
android.app.servertransaction.TransactionExecutor.executeCallbacks (TransactionExecutor.java:135)
android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:95)
android.app.ActivityThread$H.handleMessage (ActivityThread.java:2584)
android.os.Handler.dispatchMessage (Handler.java:106)
android.os.Looper.loopOnce (Looper.java:226)
android.os.Looper.loop (Looper.java:313)
android.app.ActivityThread.main (ActivityThread.java:8810)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:604)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1067)
jinliu9508 commented 2 weeks ago

@fanwgwg Is your CredentialUtils.getOneSignalId() retrieving the OneSignalId from your server, causing it to return null at times? I believe this exception can be reproduced if the OneSignalId you passed in is null. I suggest checking if OneSignalId is null before calling initWithContext.

If that’s the case, we can certainly work on making the error message clearer and more precise.

If your OneSignalId is local or guaranteed to be non-null, I would suggest a temporary workaround: use try-catch around isOptedIn while we continue investigating based on your feedback.