google / dagger

A fast dependency injector for Android and Java.
https://dagger.dev
Apache License 2.0
17.36k stars 2.01k forks source link

getViewModel crash intermittently with NullPointerException / IllegalArgumentException #4191

Open vid-stefang opened 7 months ago

vid-stefang commented 7 months ago

I'm migrating Dagger 2 to Hilt (2.48.1) and applying @AndroidEntryPoint and @HiltViewModel in my SplashScreen. In latest release, we got some crash reports in my screen: firebase-crash All of them actually mention the same thing: Screenshot from 2023-12-15 11-08-42

My SplashScreen is quite simple:

@HiltViewModel
class SplashScreenViewModel @Inject constructor(
    private val dispatcher: CustomDispatchers
) : ViewModel() {
...
}

@AndroidEntryPoint
@SuppressLint("CustomSplashScreen")
class SplashScreen : AppCompatActivity() {

    private val viewModel: SplashScreenViewModel by viewModels() // this is SplashScreen.kt line 18

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        lifecycleScope.launch { // this is SplashScreen.kt line 26
            viewModel.launchEvent.collect { ... }
        }
    }
}

// Hilt_SplashScreen.java
  @Override
  public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
    // line below is Hilt_SplashScreen.java:75
    return DefaultViewModelFactories.getActivityFactory(this, super.getDefaultViewModelProviderFactory());
  }

How can we resolve this intermittent crash in that SplashScreen?

I'm using kapt, AGP: 8.1.2, Kotlin: 1.9.10, Dagger & Hilt: 2.48.1, Java 17 And all of those crashes happen in Android 7 and 6 only (so far)

Chang-Eric commented 7 months ago

Are you able to provide more of the stacktrace? It is hard to tell what is going on.

So far based on what I can see though, I have trouble imagining that this is something in Hilt. I don't think we call dataSize() in any of our generated code, but would need the whole stacktrace to know more. Also, IllegalArgumentException by itself with no stack is very hard to know what is going on there.

vid-stefang commented 7 months ago

Sure @Chang-Eric , these are the stack traces:

// Case 1
Fatal Exception: java.lang.IllegalArgumentException:
       at android.os.Parcel.nativeAppendFrom(Parcel.java)
       at android.os.Parcel.appendFrom(Parcel.java:453)
       at android.os.BaseBundle.<init>(BaseBundle.java:126)
       at android.os.Bundle.<init>(Bundle.java:102)
       at android.content.Intent.getExtras(Intent.java:5724)
       at androidx.activity.ComponentActivity.getDefaultViewModelProviderFactory(ComponentActivity.java:654)
       at com.myapp.android.splash.Hilt_SplashScreen.getDefaultViewModelProviderFactory(Hilt_SplashScreen.java:75)
       at androidx.activity.ActivityViewModelLazyKt$viewModels$factoryPromise$2.invoke(ActivityViewModelLazy.kt:80)
       at com.myapp.android.splash.SplashScreen$special$$inlined$viewModels$default$1.invoke(ActivityViewModelLazy.kt:80)
       at com.myapp.android.splash.SplashScreen$special$$inlined$viewModels$default$1.invoke(ActivityViewModelLazy.kt:79)
       at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:47)
       at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:35)
       at com.myapp.android.splash.SplashScreen.getViewModel(SplashScreen.kt:18)
       at com.myapp.android.splash.SplashScreen.access$getViewModel(SplashScreen.kt:14)
       at com.myapp.android.splash.SplashScreen$onCreate$2.invokeSuspend(SplashScreen.kt:27)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
       at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:367)
       at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
       at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25)
       at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.java:110)
       at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126)
       at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(BuildersKt__Builders_common.kt:56)
       at kotlinx.coroutines.BuildersKt.launch(Builders.kt:1)
       at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(BuildersKt__Builders_common.kt:47)
       at kotlinx.coroutines.BuildersKt.launch$default(Builders.kt:1)
       at com.myapp.android.splash.SplashScreen.onCreate(SplashScreen.kt:26)
       at android.app.Activity.performCreate(Activity.java:6351)
       at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1114)
       at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2470)
       at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2577)
       at android.app.ActivityThread.access$1000(ActivityThread.java:166)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1414)
       at android.os.Handler.dispatchMessage(Handler.java:102)
       at android.os.Looper.loop(Looper.java:148)
       at android.app.ActivityThread.main(ActivityThread.java:5619)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:853)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:737)

// Case 2
Fatal Exception: java.lang.NullPointerException: Attempt to invoke virtual method 'int android.os.Parcel.dataSize()' on a null object reference
       at android.os.BaseBundle.<init>(BaseBundle.java:164)
       at android.os.Bundle.<init>(Bundle.java:106)
       at android.content.Intent.getExtras(Intent.java:7186)
       at androidx.activity.ComponentActivity.getDefaultViewModelProviderFactory(ComponentActivity.java:654)
       at com.myapp.android.splash.Hilt_SplashScreen.getDefaultViewModelProviderFactory(Hilt_SplashScreen.java:75)
       at androidx.activity.ActivityViewModelLazyKt$viewModels$factoryPromise$2.invoke(ActivityViewModelLazy.kt:80)
       at com.myapp.android.splash.SplashScreen$special$$inlined$viewModels$default$1.invoke(ActivityViewModelLazy.kt:80)
       at com.myapp.android.splash.SplashScreen$special$$inlined$viewModels$default$1.invoke(ActivityViewModelLazy.kt:79)
       at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:47)
       at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:35)
       at com.myapp.android.splash.SplashScreen.getViewModel(SplashScreen.kt:18)
       at com.myapp.android.splash.SplashScreen.access$getViewModel(SplashScreen.kt:14)
       at com.myapp.android.splash.SplashScreen$onCreate$2.invokeSuspend(SplashScreen.kt:27)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
       at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:367)
       at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
       at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25)
       at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.java:110)
       at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126)
       at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(BuildersKt__Builders_common.kt:56)
       at kotlinx.coroutines.BuildersKt.launch(Builders.kt:1)
       at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(BuildersKt__Builders_common.kt:47)
       at kotlinx.coroutines.BuildersKt.launch$default(Builders.kt:1)
       at com.myapp.android.splash.SplashScreen.onCreate(SplashScreen.kt:26)
       at android.app.Activity.performCreate(Activity.java:6977)
       at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1126)
       at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2946)
       at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3064)
       at android.app.ActivityThread.-wrap14(ActivityThread.java)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1659)
       at android.os.Handler.dispatchMessage(Handler.java:102)
       at android.os.Looper.loop(Looper.java:154)
       at android.app.ActivityThread.main(ActivityThread.java:6816)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1565)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1453)

additional info: Firebase tags it as "Early crashes", means it happens in the first 5 seconds when user open the app (I guess bcs it's splash screen).

Screenshot 2023-12-27 at 13 58 52 (1)
Chang-Eric commented 7 months ago

Thanks for the stacktraces. I don't think this is related to Hilt. These crashes are coming from when we call through to the Activity's getDefaultViewModelProviderFactory method which means it would happen without Hilt as well. It looks like something in the Intent extras is malformed? I'm not really sure beyond that though.

Radwa13 commented 4 months ago

Hello have you found any solution I get these exact crashes too.

nico-gonzalez commented 4 months ago

@Chang-Eric hello! we are experiencing this issue as well since some time ago in several of our activities and we were wondering if it has to do with a lifecycle issue between in the code that dagger generates for the activities annotated with @AndroidEntryPoint. In our case, the stack trace always include a reference to generated dagger.hilt.android.internal code - see stacktrace below. Let me know if you need further information

Caused by java.lang.NullPointerException
Attempt to invoke virtual method 'int android.os.Parcel.dataSize()' on a null object reference
android.os.BaseBundle.<init> (BaseBundle.java:126)
android.os.Bundle.<init> (Bundle.java:102)
android.content.Intent.getExtras (Intent.java:6569)
androidx.activity.ComponentActivity.getDefaultViewModelCreationExtras (ComponentActivity.java:655)
androidx.lifecycle.ViewModelProviderGetKt.defaultCreationExtras (ViewModelProvider.kt:359)
androidx.lifecycle.ViewModelProvider.<init> (ViewModelProvider.kt:129)
dagger.hilt.android.internal.managers.ActivityRetainedComponentManager.getViewModelProvider (ActivityRetainedComponentManager.java:100)
dagger.hilt.android.internal.managers.ActivityRetainedComponentManager.getSavedStateHandleHolder (ActivityRetainedComponentManager.java:134)
dagger.hilt.android.internal.managers.ActivityComponentManager.getSavedStateHandleHolder (ActivityComponentManager.java:76)
com.app.android.presentation.Hilt_DetailActivity.initSavedStateHandleHolder (Hilt_DetailActivity.java:53)
com.app.android.presentation.Hilt_DetailActivity.onCreate (Hilt_DetailActivity.java:64)
com.app.android.presentation.DetailActivity.onCreate (DetailActivity.kt:68)
android.app.Activity.performCreate (Activity.java:6904)
android.app.Instrumentation.callActivityOnCreate (Instrumentation.java:1136)
android.app.ActivityThread.performLaunchActivity (ActivityThread.java:3266)
android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:3415)
android.app.ActivityThread.access$1100 (ActivityThread.java:229)
android.app.ActivityThread$H.handleMessage (ActivityThread.java:1821)
android.os.Handler.dispatchMessage (Handler.java:102)
android.os.Looper.loop (Looper.java:148)
android.app.ActivityThread.main (ActivityThread.java:7406)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run (ZygoteInit.java:1230)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1120)
wanyingd1996 commented 4 months ago

Hi, nico, do you get this error if you directly invoke getDefaultViewModelCreationExtras from DetailActivity#onCreate? If so, it should be a malformed intent problem, and cannot be fixed from Dagger side, thanks!

Chang-Eric commented 4 months ago

@nico-gonzalez To my knowledge, a lifecycle issue wouldn't appear as intermittent because all of this is happening on the main thread (in your stacktrace this is happening in onCreate() which is on the main thread and if we were at the wrong time in the lifecycle, that lifecycle change we're missing would also be done on the main thread), so there wouldn't really be a race unless it were unsafe to access any ViewModels in onCreate(), even after super.onCreate(), which we know shouldn't be the case. So that's why this doesn't seem like a lifecycle issue in the generated code to me.