airbnb / mavericks

Mavericks: Android on Autopilot
https://airbnb.io/mavericks/
Apache License 2.0
5.83k stars 500 forks source link

Crash when creating MavericksViewModel #709

Closed ArunYogeshwaran closed 7 months ago

ArunYogeshwaran commented 8 months ago

Hello Team :)

We have been using Mavericks for a long time and trying to upgrade Mavericks to 3.0.7 leverage Jetpack Compose support.

Mavericks Version - 3.0.7 androidx.lifecyle-common Version - 2.7.0

This issue is related to this issue. Even after ensuring the project uses the latest androidx.lifecyle-* versions, MavericksView still wants me to implement the following

image

Still, I implemented it and launched the app, and the following runtime crash happened

Crash logs:

Process: zenjob.android.debug, PID: 17963
java.lang.RuntimeException: Unable to start activity ComponentInfo{zenjob.android.debug/zenjob.android.feature.splash.SplashActivity}: java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null i.e., before onCreateView() or after onDestroyView()
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:4164)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4322)
    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103)
    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:139)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:96)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2685)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:230)
    at android.os.Looper.loop(Looper.java:319)
    at android.app.ActivityThread.main(ActivityThread.java:8893)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:608)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103)
Caused by: java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null i.e., before onCreateView() or after onDestroyView()
    at androidx.fragment.app.Fragment.getViewLifecycleOwner(Fragment.java:377)
    at zenjob.android.feature.splash.SplashFragment.getLifecycle(SplashFragment.kt:114)
    at com.airbnb.mvrx.lifecycleAwareLazy.initializeWhenCreated(lifecycleAwareLazy.kt:46)
    at com.airbnb.mvrx.lifecycleAwareLazy.<init>(lifecycleAwareLazy.kt:37)
    at com.airbnb.mvrx.lifecycleAwareLazy.<init>(lifecycleAwareLazy.kt:19)
    at com.airbnb.mvrx.mocking.MockViewModelDelegateFactory.createLazyViewModel(MockViewModelDelegateFactory.kt:59)
    at zenjob.android.feature.splash.SplashFragment$special$$inlined$fragmentViewModel$default$2.provideDelegate(ViewModelDelegateProvider.kt:23)
    at zenjob.android.feature.splash.SplashFragment$special$$inlined$fragmentViewModel$default$2.provideDelegate(ViewModelDelegateProvider.kt:17)
    at zenjob.android.feature.splash.SplashFragment.<init>(SplashFragment.kt:38)
    at zenjob.android.feature.splash.SplashFragment$Companion.createNewInstance$splash_stagingDebug(SplashFragment.kt:170)
    at zenjob.android.feature.splash.SplashActivity.onCreate(SplashActivity.kt:33)
    at android.app.Activity.performCreate(Activity.java:8944)
    at android.app.Activity.performCreate(Activity.java:8913)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1456)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:4146)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4322) 
    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103) 
    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:139) 
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:96) 
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2685) 
    at android.os.Handler.dispatchMessage(Handler.java:106) 
    at android.os.Looper.loopOnce(Looper.java:230) 
    at android.os.Looper.loop(Looper.java:319) 
    at android.app.ActivityThread.main(ActivityThread.java:8893) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:608) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103) 

Questions:

Please help - because of this crash, we are not able to proceed with upgrading Mavericks in our project

elihart commented 8 months ago
    at zenjob.android.feature.splash.SplashFragment.getLifecycle(SplashFragment.kt:114)
    at com.airbnb.mvrx.lifecycleAwareLazy.initializeWhenCreated(lifecycleAwareLazy.kt:46)
    at com.airbnb.mvrx.lifecycleAwareLazy.<init>(lifecycleAwareLazy.kt:37)
    at com.airbnb.mvrx.lifecycleAwareLazy.<init>(lifecycleAwareLazy.kt:19)
    at com.airbnb.mvrx.mocking.MockViewModelDelegateFactory.createLazyViewModel(MockViewModelDelegateFactory.kt:59)

The lifecycle is accessed during Fragment initialization when the viewmodel property delegate is created. The lifecycle is used to manage the injected viewmodel.

Have you seen this comment and the discussion after it? https://github.com/airbnb/mavericks/issues/679#issuecomment-1716703404

That might help you fix your dependencies

ArunYogeshwaran commented 8 months ago

Hi @elihart thank you for the response.

Yes, I did see the entire discussion about the lifecycle dependencies and I made sure to update them. Also, verified them using ./gradlew -q app:dependencies > dep.txt

The error that we see here indicates Mavericks is trying to access the Fragment's viewLifecycleOwner at an inappropriate time in the Fragment's lifecycle.

Shouldn't we try to access the viewLifecycleOwner only after fragment's onCreateView and not when the fragment class is initialized? 🤔

elihart commented 7 months ago

What are you doing in your SplashFragment here?

at androidx.fragment.app.Fragment.getViewLifecycleOwner(Fragment.java:377)
    at zenjob.android.feature.splash.SplashFragment.getLifecycle(SplashFragment.kt:114

The mavericks code uses Fragment.getLifecycle, which is safe to call at this point, but your custom fragment seems to redirect it to getViewLifecycleOwner which is not safe to call at this point. That looks like your problem

ArunYogeshwaran commented 7 months ago

Good question.

I tried removing the overridden method for getLifecycle.

It crashes with InvocationTargetException now - it seems to be from the fragmentViewModel() in MavericksExtensions.kt. Please look at the logs below.

E  FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to start activity ComponentInfo{zenjob.android.debug/zenjob.android.feature.splash.SplashActivity}: java.lang.reflect.InvocationTargetException
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3645)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3782)
    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:101)
    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2307)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:201)
    at android.os.Looper.loop(Looper.java:288)
    at android.app.ActivityThread.main(ActivityThread.java:7872)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
Caused by: java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Method.invoke(Native Method)
    at com.airbnb.mvrx.MavericksFactoryKt.createViewModel(MavericksFactory.kt:49)
    at com.airbnb.mvrx.MavericksFactoryKt.access$createViewModel(MavericksFactory.kt:1)
    at com.airbnb.mvrx.MavericksFactory.create(MavericksFactory.kt:22)
    at androidx.lifecycle.ViewModelProvider$Factory.create(ViewModelProvider.kt:83)
    at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:184)
    at com.airbnb.mvrx.MavericksViewModelProvider.get(MavericksViewModelProvider.kt:63)
    at com.airbnb.mvrx.MavericksViewModelProvider.get$default(MavericksViewModelProvider.kt:31)
    at zenjob.android.feature.splash.SplashFragment$special$$inlined$fragmentViewModel$default$1.invoke(MavericksExtensions.kt:42)
    at zenjob.android.feature.splash.SplashFragment$special$$inlined$fragmentViewModel$default$1.invoke(MavericksExtensions.kt:37)
    at com.airbnb.mvrx.mocking.MockViewModelDelegateFactory.getMockedViewModel(MockViewModelDelegateFactory.kt:161)
    at com.airbnb.mvrx.mocking.MockViewModelDelegateFactory.access$getMockedViewModel(MockViewModelDelegateFactory.kt:28)
    at com.airbnb.mvrx.mocking.MockViewModelDelegateFactory$createLazyViewModel$2$1.invoke(MockViewModelDelegateFactory.kt:62)
    at com.airbnb.mvrx.mocking.MockViewModelDelegateFactory$createLazyViewModel$2$1.invoke(MockViewModelDelegateFactory.kt:61)
    at com.airbnb.mvrx.mocking.MockMavericksViewModelConfigFactory.withMockBehavior(MockableMavericksViewModelConfig.kt:239)
    at com.airbnb.mvrx.mocking.MockViewModelDelegateFactory$createLazyViewModel$2.invoke(MockViewModelDelegateFactory.kt:61)
    at com.airbnb.mvrx.mocking.MockViewModelDelegateFactory$createLazyViewModel$2.invoke(MockViewModelDelegateFactory.kt:95)
    at com.airbnb.mvrx.lifecycleAwareLazy.getValue(lifecycleAwareLazy.kt:79)
    at com.airbnb.mvrx.lifecycleAwareLazy$initializeWhenCreated$1.onCreate(lifecycleAwareLazy.kt:52)
    at androidx.lifecycle.DefaultLifecycleObserverAdapter.onStateChanged(DefaultLifecycleObserverAdapter.kt:24)
    at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.kt:322)
    at androidx.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.kt:258)
    at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.kt:294)
    at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.kt:143)
    at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.kt:126)
    at androidx.fragment.app.Fragment.performCreate(Fragment.java:3096)
    at androidx.fragment.app.FragmentStateManager.create(FragmentStateManager.java:475)
    at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:257)
    at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1890)
elihart commented 7 months ago

Is there more to the stacktrace that you didn't share? The InvocationTargetException is just a wrapper with an underlying cause, which should be at the bottom of the stacktrace

I believe the crash happens during this reflection

   factoryClass.getMethod("create", ViewModelContext::class.java, MavericksState::class.java)
                .invoke(factoryClass.instance(), viewModelContext, initialState) as VM?

This is working for everyone else, so something is likely wrong with you viewmodel set up

ArunYogeshwaran commented 7 months ago

@elihart Thanks for the suggestions. There was a crash becasue FragmentActivity couldn't be cast to ComponentActivity and now I changed it to ComponentActivity which fixed the issue. So, I'm closing this issue now.