rickclephas / KMP-ObservableViewModel

Library to use AndroidX/Kotlin ViewModels with SwiftUI
MIT License
569 stars 29 forks source link

KMMViewModel being `clear`ed unexpectedly #64

Closed jollygreenegiant closed 4 months ago

jollygreenegiant commented 4 months ago

I'm getting a lot of errors in production relating to ktor calls being cancelled in the viewmodel when the viewModelScope is cleared.

I have the following stack trace:

Non-fatal Exception: io.github.jan.supabase.exceptions.HttpRequestException
HTTP request to http://localhost?user_id=eq.<user_id>&reading_status=eq.FINISHED&finish_date=gte.2024-01-01T00%3A00&select=%2A (GET) failed with message: Job was cancelled

io.github.jan.supabase.network.KtorSupabaseHttpClient.request (KtorSupabaseHttpClient.kt:55)
io.github.jan.supabase.network.KtorSupabaseHttpClient$request$1.invokeSuspend (KtorSupabaseHttpClient.kt:12)
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:102)
kotlinx.coroutines.EventLoop.processUnconfinedEvent (EventLoop.common.kt:65)
kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined (DispatchedTask.kt:241)
kotlinx.coroutines.DispatchedTaskKt.dispatch (DispatchedTask.kt:159)
kotlinx.coroutines.CancellableContinuationImpl.dispatchResume (CancellableContinuationImpl.kt:470)
kotlinx.coroutines.CancellableContinuationImpl.cancel (CancellableContinuationImpl.kt:213)
kotlinx.coroutines.CancellableContinuationImpl.parentCancelled$kotlinx_coroutines_core (CancellableContinuationImpl.kt:220)
kotlinx.coroutines.ChildContinuation.invoke (JobSupport.kt:1447)
kotlinx.coroutines.JobSupport.notifyCancelling (JobSupport.kt:1473)
kotlinx.coroutines.JobSupport.tryMakeCancelling (JobSupport.kt:796)
kotlinx.coroutines.JobSupport.makeCancelling (JobSupport.kt:756)
kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core (JobSupport.kt:672)
kotlinx.coroutines.JobSupport.parentCancelled (JobSupport.kt)
kotlinx.coroutines.ChildHandleNode.invoke (JobSupport.kt:1436)
kotlinx.coroutines.JobSupport.notifyCancelling (JobSupport.kt:1473)
kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath (JobSupport.kt:901)
kotlinx.coroutines.JobSupport.tryMakeCompleting (JobSupport.kt:864)
kotlinx.coroutines.JobSupport.cancelMakeCompleting (JobSupport.kt:697)
kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core (JobSupport.kt:668)
kotlinx.coroutines.JobSupport.cancelInternal (JobSupport.kt)
kotlinx.coroutines.JobSupport.cancel (JobSupport.kt:618)
kotlinx.coroutines.JobKt__JobKt.cancel (Job.kt:560)
kotlinx.coroutines.JobKt.cancel (Job.kt)
kotlinx.coroutines.JobKt__JobKt.cancel$default (Job.kt:559)
kotlinx.coroutines.JobKt.cancel$default (Job.kt)
androidx.lifecycle.CloseableCoroutineScope.close (ViewModel.kt:51)
androidx.lifecycle.ViewModel.closeWithRuntimeException (ViewModel.kt:252)
androidx.lifecycle.ViewModel.clear (ViewModel.java:189)
androidx.lifecycle.ViewModelStore.clear (ViewModelStore.kt:69)
androidx.navigation.NavControllerViewModel.clear (NavControllerViewModel.kt:33)
androidx.navigation.NavController$NavControllerNavigatorState.markTransitionComplete (NavController.kt:359)
androidx.navigation.NavController.unlinkChildFromParent$navigation_runtime_release (NavController.kt:163)
androidx.navigation.NavController$NavControllerNavigatorState.markTransitionComplete (NavController.kt:352)
androidx.navigation.compose.ComposeNavigator.onTransitionComplete (ComposeNavigator.kt:82)
androidx.navigation.compose.NavHostKt$NavHost$15.invokeSuspend (NavHost.kt:314)
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:104)
androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch (AndroidUiDispatcher.android.kt:81)
androidx.compose.ui.platform.AndroidUiDispatcher.access$setScheduledFrameDispatch$p (AndroidUiDispatcher.android.kt)
androidx.compose.ui.platform.AndroidUiDispatcher.access$performTrampolineDispatch (AndroidUiDispatcher.android.kt)
androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.run (AndroidUiDispatcher.android.kt:57)
android.os.Handler.handleCallback (Handler.java:942)
android.os.Handler.dispatchMessage (Handler.java:99)
android.os.Looper.loopOnce (Looper.java:226)
android.os.Looper.loop (Looper.java:313)
android.app.ActivityThread.main (ActivityThread.java:8757)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:571)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1067)

These lines in particular make it look like the ViewModel is being cleared, and the coroutine that launches the database call is being cancelled and the data is lost:

androidx.lifecycle.ViewModel.closeWithRuntimeException (ViewModel.kt:252)
androidx.lifecycle.ViewModel.clear (ViewModel.java:189)
androidx.lifecycle.ViewModelStore.clear (ViewModelStore.kt:69)
androidx.navigation.NavControllerViewModel.clear (NavControllerViewModel.kt:33)
androidx.navigation.NavController$NavControllerNavigatorState.markTransitionComplete (NavController.kt:359)
androidx.navigation.NavController.unlinkChildFromParent$navigation_runtime_release (NavController.kt:163)
androidx.navigation.NavController$NavControllerNavigatorState.markTransitionComplete (NavController.kt:352)
androidx.navigation.compose.ComposeNavigator.onTransitionComplete (ComposeNavigator.kt:82)
androidx.navigation.compose.NavHostKt$NavHost$15.invokeSuspend (NavHost.kt:314)

Users are seeing this on app startup on the home screen, and seemingly randomly. Some have reported that killing and restarting the app fixes it temporarily. Some have reinstalled and said that works for a little while, but the issue seems to be happening intermittently and I'm not able to reproduce it consistently on my end.

Any idea what might be causing this?

jollygreenegiant commented 4 months ago

Relevant discussion over in the Supabase repo (which I thought might be the cause initially): https://github.com/supabase-community/supabase-kt/discussions/549#discussion-6489297

rickclephas commented 4 months ago

Hmm, based on the stacktrace, it seems compose thinks it's time to clear your viewmodels. I am not sure why that would be the case though.

KMM-ViewModel is really basic on Android. It just exposes the required classes to common Kotlin code. I would be very surprised if this is somehow caused by the library.

I would expect this to be either an edge case in your compose (navigation) code, or a bug in compose itself.

jollygreenegiant commented 4 months ago

That's what I figured - I'm wondering if it could also be an issue with Koin and how the viewmodels are scoped. I'll investigate further, thank you.