Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - java.lang.IllegalStateException: Method addObserver must be called on the main thread

An exception is thrown when trying to access the viewmodel. The exception complains about calling addObserver, but I don't see anything in the codepath that may be calling addObserver Fragment

@AndroidEntryPoint
class ReferralEntryDialogFragment : DialogFragment() {

    private val viewModel: ReferralEntryViewModel by viewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.applyCodeButton.setOnClickListener {
            GlobalScope.launch {
                try {
                    // Exception is thrown as soon as viewModel is accesses
                    // It does not get to applyCode function
                    viewModel.applyCode("code)
                } catch (e: Exception) { }
            }
        }
    }
}

ViewModel

@HiltViewModel
class ReferralEntryViewModel @Inject constructor() : ViewModel() {
    suspend fun applyCode(code: String): ApplyReferralResponse {
        val result = Firebase.functions.getHttpsCallable("applyReferralCode")
            .call(mapOf( "referralCode" to code))
            .await()

        val map = result?.data as Map<String, Any>
        val jsonString = Gson().toJson(map)
        return Gson().fromJson(jsonString, ApplyReferralResponse::class.java)
    }
}

Stack trace

result = {StackTraceElement[20]@26993} 
 0 = {StackTraceElement@26901} "androidx.lifecycle.LifecycleRegistry.enforceMainThreadIfNeeded(LifecycleRegistry.java:317)"
 1 = {StackTraceElement@26902} "androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:172)"
 2 = {StackTraceElement@26903} "androidx.lifecycle.SavedStateHandleController.attachToLifecycle(SavedStateHandleController.java:49)"
 3 = {StackTraceElement@26904} "androidx.lifecycle.SavedStateHandleController.create(SavedStateHandleController.java:70)"
 4 = {StackTraceElement@26905} "androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:67)"
 5 = {StackTraceElement@26906} "androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:84)"
 6 = {StackTraceElement@26907} "dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:107)"
 7 = {StackTraceElement@26908} "androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:187)"
 8 = {StackTraceElement@26909} "androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)"
 9 = {StackTraceElement@26910} "androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:54)"
 10 = {StackTraceElement@26911} "androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:41)"
 11 = {StackTraceElement@26912} "com.shivaapps.freeloaders.referral.ReferralEntryDialogFragment.getViewModel(Unknown Source:2)"
 12 = {StackTraceElement@26913} "com.shivaapps.freeloaders.referral.ReferralEntryDialogFragment.access$getViewModel$p(ReferralEntryDialogFragment.kt:18)"
 13 = {StackTraceElement@26914} "com.shivaapps.freeloaders.referral.ReferralEntryDialogFragment$onViewCreated$1$1.invokeSuspend(ReferralEntryDialogFragment.kt:41)"
 14 = {StackTraceElement@26915} "kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)"
 15 = {StackTraceElement@26916} "kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)"
 16 = {StackTraceElement@26917} "kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)"
 17 = {StackTraceElement@26918} "kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)"
 18 = {StackTraceElement@26919} "kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)"
 19 = {StackTraceElement@26920} "kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)"
like image 675
aryaxt Avatar asked Dec 30 '22 15:12

aryaxt


1 Answers

The viewmodel is created lazily upon access, and creation of the viewmodel must be done on the main thread. Simply trying to access the viewmodel on the main thread before accessing it on the global scope solves the problem

// Access viewmodel so that it gets initialized on the main thread
viewModel

GlobalScope.launch
  try {
      // Exception is thrown as soon as viewModel is accesses
      // It does not get to applyCode function
      viewModel.applyCode("code)
   } catch (e: Exception) {}
}
like image 102
aryaxt Avatar answered Jan 02 '23 06:01

aryaxt