CoroutineExceptionHandler doesn't work with ViewModelScope and Koin - android

I have a method to fetch devices using coroutines and use viewModelScope.launch to run coroutines. I want to catch errors using CoroutineExceptionHandler, but I get the error:
E/[Koin]: Instance creation error : could not create instance for [Factory:'com.test.presentation.viewModel.ActivateDeviceViewModel']: java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter context
kotlin.coroutines.CombinedContext.plus(Unknown Source:2)
kotlinx.coroutines.CoroutineContextKt.newCoroutineContext(CoroutineContext.kt:33)
kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:52)
kotlinx.coroutines.BuildersKt.launch(Unknown Source:1)
kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:47)
kotlinx.coroutines.BuildersKt.launch$default(Unknown Source:1)
com.test.presentation.viewModel.ActivateDeviceViewModel.fetchDevices(ActivateDeviceViewModel.kt:42)
com.test.presentation.viewModel.ActivateDeviceViewModel.<init>(ActivateDeviceViewModel.kt:29)
com.test.di.ModulesKt$viewModelModule$1$2.invoke(Modules.kt:63)
com.test.di.ModulesKt$viewModelModule$1$2.invoke(Modules.kt:63)
org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:51)
org.koin.core.instance.FactoryInstanceFactory.get(FactoryInstanceFactory.kt:36)
org.koin.core.registry.InstanceRegistry.resolveInstance$koin_core(InstanceRegistry.kt:103)
org.koin.core.scope.Scope.resolveInstance(Scope.kt:236)
org.koin.core.scope.Scope.access$resolveInstance(Scope.kt:34)
org.koin.core.scope.Scope$get$1.invoke(Scope.kt:199)
org.koin.core.time.MeasureKt.measureDurationForResult(Measure.kt:75)
org.koin.core.scope.Scope.get(Scope.kt:198)
com.test.presentation.fragment.ActivateDeviceFragment$special$$inlined$inject$default$1.invoke(ComponentCallbackExt.kt:69)
kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
com.test.presentation.fragment.ActivateDeviceFragment.getViewModel(ActivateDeviceFragment.kt:39)
com.test.presentation.fragment.ActivateDeviceFragment.access$getViewModel(ActivateDeviceFragment.kt:37)
com.test.presentation.fragment.ActivateDeviceFragment$handleActivateDeviceResult$1$1.invokeSuspend(ActivateDeviceFragment.kt:51)
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
androidx.lifecycle.DispatchQueue.drainQueue(DispatchQueue.kt:75)
androidx.lifecycle.DispatchQueue.resume(DispatchQueue.kt:54)
androidx.lifecycle.LifecycleController$observer$1.onStateChanged(LifecycleController.kt:40)
androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:354)
androidx.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.java:265)
androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:307)
androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:148)
androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:134)
androidx.fragment.app.Fragment.performStart(Fragment.java:3024)
androidx.fragment.app.FragmentStateManager.start(FragmentStateManager.java:568)
androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:277)
androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1327)
androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2757)
androidx.fragment.app.FragmentManager.dispatchStart(FragmentManager.java:2707)
androidx.fragment.app.Fragment.performStart(Fragment.java:3028)
androidx.fragment.app.FragmentStateManager.start(FragmentStateManager.java:568)
androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:277)
androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1327)
androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2757)
androidx.fragm.
Please tell me what am I doing wrong and how to fix it? Thanks
fragment
class ActivateDeviceFragment : Fragment(R.layout.fragment_activate_device) {
private val viewModel: ActivateDeviceViewModel by inject()
// ....
}
view model
class ActivateDeviceViewModel constructor(
private val activateDeviceUseCase: ActivateDeviceUseCase,
) : ViewModel() {
init {
fetchDevices()
}
private val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
Timber.tag("test").d(throwable)
}
private fun fetchDevices() {
viewModelScope.launch(exceptionHandler) {
}
}
}
koin modules
val useCaseModule = module {
single { ActivateDeviceUseCase() }
}
val viewModelModule = module {
viewModel { ActivateDeviceViewModel(get() }
}

It is not related to Koin.
The log shows that the exceptionHandler in viewModelScope.launch(exceptionHandler) is actually null, and which throws NullPointerException.
It is because you trigger the fetchDevices() inside init{}, in which the instantiation of exceptionHandler is not ready (means it is null).
Therefore, you should not call fetchDevices() inside init{}. For example, you could call this in your Fragment onViewCreated(){ viewModel.fetchDevices } instead. It will be fine.

Related

Kotlin MutableStateFlow of RealmUUID? throwing exception on emitting null

MutableStateFlow<RealmUUID?> throws exception when trying to emit null second time.
#Test
fun mutableStateFlow_null_test() {
val flow = MutableStateFlow<RealmUUID?>(null)
runBlocking {
this.launch {
flow.collect {
println(it)
}
}
for (i in 0 .. 10) {
flow.tryEmit(null)
delay(100)
flow.tryEmit(RealmUUID.random())
delay(100)
}
}
}
It throws
class kotlinx.coroutines.internal.Symbol cannot be cast to class io.realm.kotlin.types.RealmUUID (kotlinx.coroutines.internal.Symbol and io.realm.kotlin.types.RealmUUID are in unnamed module of loader 'app')
java.lang.ClassCastException: class kotlinx.coroutines.internal.Symbol cannot be cast to class io.realm.kotlin.types.RealmUUID (kotlinx.coroutines.internal.Symbol and io.realm.kotlin.types.RealmUUID are in unnamed module of loader 'app')
at io.realm.kotlin.internal.RealmUUIDImpl.equals(RealmUUIDImpl.kt:61)
at kotlin.jvm.internal.Intrinsics.areEqual(Intrinsics.java:167)
at kotlinx.coroutines.flow.StateFlowImpl.updateState(StateFlow.kt:329)
at kotlinx.coroutines.flow.StateFlowImpl.setValue(StateFlow.kt:318)
at kotlinx.coroutines.flow.StateFlowImpl.tryEmit(StateFlow.kt:370)
at com.test.app.ExampleUnitTest$mutableStateFlow_null_test$1.invokeSuspend(ExampleUnitTest.kt:39)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:234)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
at
This only happens with MutableStateFlow<RealmUUID?> but works fine with any primitives or data class or even with data class TestData(val realmUUID : RealmUUID?) and MutableStateFlow<TestData?>(null)

Kotlin coroutines: Attempt to invoke virtual method 'void androidx.lifecycle.MutableLiveData.postValue(java.lang.Object)' on a null object reference

I have some errors in Retrofit2 and Kotlin Coroutines technologies. I need to dynamically query an info in my service.
For example, the URL is "https://exampleapidomain.com/api/sub_categories/read.php?id=2" I want to change id parameter dynamically.
My service:
interface AltKategoriService {
#GET("alt_kategoriler/" + Const.READ_URL_PIECE)
fun getId(#Query("id") id: String?): Call<Resource<AltKategorilerResponse>>
companion object{
fun build(): AltKategoriService {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(interceptor)
.build()
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(Const.BASE_URL)
.client(okHttpClient)
.build()
return retrofit.create(AltKategoriService::class.java)
}
}
}
My DataSource file:
class RemoteAltKategorilerDataSource : AltKategorilerDataSource {
override fun getSubCategories(): Flow<Resource<AltKategorilerResponse>> = flow {
try {
emit(Resource.Loading())
val call = AltKategoriService.build().getId("2").execute()
if (call.isSuccessful) {
call.body()?.let {
emit(it)
}
}
} catch (ex: Exception) {
emit(Resource.Error(ex))
ex.printStackTrace()
}
}
}
I get the following error:
Attempt to invoke virtual method 'void androidx.lifecycle.MutableLiveData.postValue(java.lang.Object)' on a null object reference" and then, app crashes.
I'm waiting for your answers and code examples. Thank you!
Edited. My ViewModel:
class SubCategoryViewModel: ViewModel() {
private val altKategoriRepository = AltKategoriRepository()
init {
getUsers()
}
var loading: MutableLiveData<Boolean>? = MutableLiveData()
var altKategoriLiveData = MutableLiveData<AltKategorilerResponse>()
var error = MutableLiveData<Throwable>()
fun getUsers() = viewModelScope.launch {
altKategoriRepository.getSubCategories()
.asLiveData(viewModelScope.coroutineContext).observeForever {
when (it.status) {
ResourceStatus.LOADING -> {
loading?.postValue(true)
}
ResourceStatus.SUCCESS -> {
Log.e("Message", it.data.toString())
altKategoriLiveData.postValue(it.data!!)
loading?.postValue(false)
}
ResourceStatus.ERROR -> {
error.postValue(it.throwable!!)
loading?.postValue(false)
}
}
}
}
}
Kotlin class initialisation takes place in the following order:
primary constructor -> init block -> secondary constructor
As no initialisation is done neither for var loading, var altKategoriLiveData nor var error class members of SubCategoryViewModel by the time getUsers() is called in the init { } block, you get the exception resulting in the app crash.
Regarding your implementation of the MVVM pattern, it contradicts to that of the official Android documentation, where a View is supposed to call a corresponding method of ViewModel explicitly or implicitly.
It should also work by just moving the init { } after the variable declarations or if you declare your loading state directly as LOADING. Also i think it's fine to declare it inside the init block, the documentation doesn't refer to that as being at fault if i'm reading correctly. Also it helps doing the call in init to avoid multiple times loading in Activities or Fragments if you only need to do the call once when the viewmodel is created and avoiding multiple calls by e.g: orientation change or other lifecycle dependent things. but please correct me if i'm wrong.

Android: Impossible NullPointerException when using viewModelscope and withContext

My problem is, that I get an impossible NullPointerException. When I access my emailEntity data from my price variable without using an elvis-operator, my price variable gets null and I get a NullPointerException.
Now comes the problem: When I use an elvis-operator at my price variable and access my emailEntity data within a function, I do not get a NullPointerException and the price is correctly set. What am I doing wrong?
Base Code
class EmailViewModel #ViewModelInject constructor() : ViewModel() {
// This is the value I access from my price variable and the function
private val emailEntity = MutableLiveData<EmailEntity?>()
// Setting the value of my emailEntity here
init {
// I have to use viewModelScope because "getCalibratePrice and getRepairPrice" are suspend functions
viewModelScope.launch {
withContext(Dispatchers.IO) {
when(subject.value.toString()) {
"Toast" -> emailEntity.postValue(emailRepository.getCalibratePrice())
else -> emailEntity.postValue(emailRepository.getRepairPrice())
}
}
}
}
}
Problem Code
// NullPointerException
val price = MutableLiveData(emailEntity.value?.basePrice!!)
fun checkIfPriceIsInitialized() {
Timber.d("Emailprice is ${emailEntity.value.basePrice}")
}
Working Code
// NO NullPointerException but value is now always 0F
val price = MutableLiveData(emailEntity.value?.basePrice ?: 0F)
// EmailEntity price is correctly set here!!!
fun checkIfPriceIsInitialized() {
Timber.d("Emailprice is ${emailEntity.value.basePrice}")
}
StackTrace
java.lang.NullPointerException
at com.example.app.framework.ui.viewmodel.EmailViewModel.<init>(EmailViewModel.kt:164)
at com.example.app.framework.ui.viewmodel.EmailViewModel_AssistedFactory.create(EmailViewModel_AssistedFactory.java:58)
at com.example.app.framework.ui.viewmodel.EmailViewModel_AssistedFactory.create(EmailViewModel_AssistedFactory.java:20)
at androidx.hilt.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:76)
at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:69)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:185)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:54)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:41)
at com.example.app.framework.ui.view.fragments.home.calibrateAndRepair.CalibrateRepairMessageFragment.getViewModel(Unknown Source:2)
at com.example.app.framework.ui.view.fragments.home.calibrateAndRepair.CalibrateRepairMessageFragment.getViewModel(CalibrateRepairMessageFragment.kt:26)
at com.example.app.framework.ui.view.basefragments.BaseFragment.onCreateView(BaseFragment.kt:30)
at com.example.app.framework.ui.view.basefragments.EmailFragment.onCreateView(EmailFragment.kt:54)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2699)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:320)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1199)
at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2236)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2009)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1965)
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1861)
at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:413)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
EmailViewModel.<init>(EmailViewModel.kt:164) points to -> val price = MutableLiveData(emailEntity.value?.basePrice!!)
Please bear in mind that I started with kotlin coroutines from scratch. Therefore I do not know 100% how it all really works
EDIT
This is my repository:
interface EmailRepository {
fun sendEmail(email: Email): Flow<EmailStatus<Unit>>
suspend fun getCalibratePrice(): Flow<EmailEntity?>
suspend fun getRepairPrice(): Flow<EmailEntity?>
}
And this is my implementation:
class EmailRepositoryImpl #Inject constructor(
private val db: FirebaseFirestore
) : EmailRepository {
override suspend fun getCalibratePrice(): Flow<EmailEntity?> = flow {
val result = db.collection("emailprice").document("Kalibrieren").get().await()
val emailEntity = result.toObject<EmailEntity?>()
emit(emailEntity)
}.catch {
Timber.d("Error on getCalibrate Price")
}.flowOn(Dispatchers.Main)
override suspend fun getRepairPrice(): Flow<EmailEntity?> = flow {
val collection = db.collection("emailprice").document("Reparieren").get().await()
val emailEntity = collection.toObject<EmailEntity?>()
emit(emailEntity)
}.catch {
Timber.d("Error on getRepairPrice")
}.flowOn(Dispatchers.Main)
}
The alternative would be to use .single() at the end and change the return type from Flow<EmailEntity?> to EmailEntity
EDIT 2
private var emailEntity: EmailEntity = EmailEntity("", 50F)
init {
viewModelScope.launch {
when(subject.value.toString()) {
context.getString(R.string.home_calibrate_card_headline) -> emailRepository.getCalibratePrice().collect {
emailEntity = it ?: EmailEntity("Error", 100F)
}
else -> emailRepository.getRepairPrice().collect {
emailEntity = it ?: EmailEntity("Error Zwei", 150F)
}
}
}
}
// Price is 50 and does not change..
val price = MutableLiveData(emailEntity.basePrice)
Your coroutine is running asynchronous code. By the time your EmailViewModel is instantiated, the coroutine hasn't finished running yet, so at that point, the value of the LiveData is still null. You must be trying to retrieve the value immediately from your main thread, before the coroutine finishes running.
Typically with a LiveData, you almost never retrieve a value directly. Instead, you should observe the LiveData with a callback so you can react when it gets a value, which is not going to happen immediately with suspend functions and coroutines.
By the way, you should only update LiveData from the main thread, which you are failing to do with your coroutine. Assuming your suspend functions properly delegate to background threads, which is the case if they're from you using the Room library, you should remove the wrapping withContext block from your coroutine. A properly composed suspend function can always be called from the Main Dispatcher safely, and will delegate to background dispatchers as necessary internally.

No definition found for class:'yodgorbek.komilov.musobaqayangiliklari.repository.BBCRepository' & qualifier:'bbcModules'. Check your definitions?

I am developing news android app and I have implemented Koin modules but I am getting the following exception
executor.executeLifecycleState(TransactionExecutor.java:147)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:73)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1858)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:201)
at android.app.ActivityThread.main(ActivityThread.java:6820)
... 3 more
Caused by: org.koin.core.error.NoBeanDefFoundException: No definition found for class:'yodgorbek.komilov.musobaqayangiliklari.repository.BBCRepository' & qualifier:'bbcModules'. Check your definitions!
at org.koin.core.scope.Scope.throwDefinitionNotFound(Scope.kt:247)
at org.koin.core.scope.Scope.resolveInstance(Scope.kt:216)
at org.koin.core.scope.Scope.get(Scope.kt:181)
at yodgorbek.komilov.musobaqayangiliklari.di.application.module.BbcModulesKt$bbcModules$1$2.invoke(bbcModules.kt:16)
at yodgorbek.komilov.musobaqayangiliklari.di.application.module.BbcModulesKt$bbcModules$1$2.invoke(Unknown Source:4)
at org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:50)
... 48 more
below my bbcModules.kt where I have implemented BBCSportViewModel logic
val bbcModules = module {
factory(named("bbcModules")) { (BBCRepositoryImpl(bbcsportNewsApi = get())) }
// Tells Koin how to create an instance of BBCRepository
viewModel { BBCSportViewModel(bbcRepository = get(named("bbcModules")))
}
}
below BBCRepository.kt
interface BBCRepository {
// Suspend is used to await the result from Deferred
suspend fun getBBCList(): UseCaseResult<List<Article>>
}
#Suppress("UNCHECKED_CAST")
class BBCRepositoryImpl(private val bbcsportNewsApi: SportNewsInterface) : BBCRepository {
override suspend fun getBBCList(): UseCaseResult<List<Article>> {
return try {
val result = bbcsportNewsApi.getBBCSport().body()!!.articles
UseCaseResult.Success(result)
} catch (ex: Exception) {
UseCaseResult.Error(ex)
}
}
}
I want to know where exactly I am making mistake what I have to fix exception I have followed many StackOverflow answer it did not solve my problem
The stacktrace says that no definition for BBCRepository was found.
In your koin module you have a factory of BBCRepositoryImpl and not BBCRepository:
factory(named("bbcModules")) {
(BBCRepositoryImpl(bbcsportNewsApi = get()))
}
In order for it to work, you should make sure you are providing the interface in koin with:
factory<BBCRepository>(named("bbcModules")) {
BBCRepositoryImpl(bbcsportNewsApi = get())
}
or
factory(named("bbcModules")) {
(BBCRepositoryImpl(bbcsportNewsApi = get())) as BBCRepository
}

Error accessing Room Database using RxJava, with Use Cases and Clean Architecure

I'm building an app using clean architecture.
I'm getting an error when calling an use case from my ViewModel that needs to run a query on my Room Database, telling me I'm accessing it in the main thread.
Caused by: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
at android.arch.persistence.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:204)
at android.arch.persistence.room.RoomDatabase.query(RoomDatabase.java:232)
at com.something.data.db.PatientDAO_Impl.getAllPatients(PatientDAO_Impl.java:229)
at com.something.data.repositories.dataSource.PatientDataStoreFactory.create(PatientDataStoreFactory.kt:18)
at com.something.data.repositories.PatientRepositoryImplementation.getPatient(PatientRepositoryImplementation.kt:97)
at com.something.domain.useCases.GetPatientUseCase.execute(GetPatientUseCase.kt:10)
at com.something.some_thing.searchForm.SearchFormViewModel.getPatientFromDb(SearchFormViewModel.kt:49)
at com.something.some_thing.searchForm.SearchFormFragment.onCreateView(SearchFormFragment.kt:99)
at android.support.v4.app.Fragment.performCreateView(Fragment.java:2439)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1460)
at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1784)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1852)
at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:802)
at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2625)
at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2411)
at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2366)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2273)
at android.support.v4.app.FragmentManagerImpl.dispatchStateChange(FragmentManager.java:3273)
at android.support.v4.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.java:3229)
at android.support.v4.app.FragmentController.dispatchActivityCreated(FragmentController.java:201)
at android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:620)
at android.support.v7.app.AppCompatActivity.onStart(AppCompatActivity.java:178)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1334)
at android.app.Activity.performStart(Activity.java:7057)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2819)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2931) 
at android.app.ActivityThread.-wrap11(Unknown Source:0) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1620) 
at android.os.Handler.dispatchMessage(Handler.java:105) 
at android.os.Looper.loop(Looper.java:173) 
at android.app.ActivityThread.main(ActivityThread.java:6698) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:782) 
Here is some code
My ViewModel call:
fun getPatient(){
val disposable = getPatientUseCase.execute()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::getPatientSuccess, this::onError)
compositeDisposable.add(disposable)
}
My Use Case:
class GetPatientUseCase(private val patientRepository: PatientRepository){
fun execute(): Single<Patient> {
return patientRepository.getPatient()
}
My repository method implementation
override fun getPatient(): Single<Patient> {
val patientDataStore = PatientDataStoreFactory().create()
return patientDataStore.patient()
}
This is where the crash happens, in My PatientDataStoreFactory, when running db.patientDAO().getAllPatients().isNotEmpty():
#Singleton
class PatientDataStoreFactory {
private val db = DaggerWrapper.getInstance().database()
fun create(): PatientDataStore {
return if (db.patientDAO().getAllPatients().isNotEmpty()){
LocalPatientDataStore()
} else {
CloudPatientDataStore()
}
}
}
In my ViewModel I'm subscribing the use case execution so I dont understand this crash...
Any ideas?
The problem is here: db.patientDAO().getAllPatients(). You are calling getAllPatients() on the UI Thread. You should wrap in in the Single and subscribe on Schedulers.io().
One of the possible solutions is to rewrite the following code:
val patientDataStore = PatientDataStoreFactory().create()
return patientDataStore.patient()
to something like this:
return Single.fromCallable { PatientDataStoreFactory().create() }
.flatMap { store -> store.patient() }

Categories

Resources