Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

java.lang.IllegalStateException: Cannot invoke observeForever on a background thread

Can someone help me find where I am going wrong here. I need to continously observer network data and update the UI whenever there is a data change from the Worker. Please note that this was working before upgrading to androidx.

Here is a Worker class.

class TestWorker(val context: Context, val params: WorkerParameters): Worker(context, params){

    override fun doWork(): Result {
        Log.d(TAG, "doWork called")
        val networkDataSource = Injector.provideNetworkDataSource(context)
        networkDataSource.fetchData(false)

        return Worker.Result.SUCCESS
    }

    companion object {
        private const val TAG = "MY_WORKER"
    }

}

Which is called as follows:

fun scheduleRecurringFetchDataSync() {
    Log.d("FETCH_SCHEDULER", "Scheduling started")

    val fetchWork = PeriodicWorkRequest.Builder(TestWorker::class.java, 1, TimeUnit.MINUTES)
            .setConstraints(constraints())
            .build()
    WorkManager.getInstance().enqueue(fetchWork)
}

private fun constraints(): Constraints{
    return Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .setRequiresBatteryNotLow(true)
            .build()
}

I also have a UserDao and UserRepository to fetch and store data. I am observing the network data in the UserRepository as follows:

class UserRepository (
    private val userDao: UserDao,
    private val networkDataSource: NetworkDataSource,
    private val appExecutors: AppExecutors){

init {
    val networkData= networkDataSource.downloadedData
    networkData.observeForever { newData->
        appExecutors.diskIO().execute {
            userDao.insert(newData.user)
        }
    }}

Can someone help me locate where I am going wrong. This is giving me error as follows:

java.lang.IllegalStateException: Cannot invoke observeForever on a background thread
    at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:443)
    at androidx.lifecycle.LiveData.observeForever(LiveData.java:204)
    at com.example.app.data.repo.UserRepository.<init>(UserRepository.kt:17)
    at com.example.app.data.repo.UserRepository$Companion.getInstance(UserRepository.kt:79)
like image 274
FreddCha Avatar asked Oct 03 '18 08:10

FreddCha


4 Answers

Change this:

networkData.observeForever { newData->
    appExecutors.diskIO().execute {
        userDao.insert(newData.user)
    }
}

To:

Variant B (with coroutines):

GlobalScope.launch(Dispatchers.Main) { networkData.observerForever { /*..*/ } }

But be aware, the usage of GlobalScope is not recommended: https://stackoverflow.com/a/54351785/1185087

Variant A (without coroutines):

Handler(Looper.getMainLooper()).post { networkData.observeForever{ /*..*/ } }

Explanation

Normally observe(..) and observeForever(..) should be called from the main thread because their callbacks (Observer<T>.onChanged(T t)) often change the UI which is only possible in the main thread. That's the reason why android checks if the call of the observe functions is done by the main thread.

In your case UserRepository.init{} is called by a background thread, so the exception is thrown. To switch back to the main thread you can use one of the above variants. But be aware the code inside of your observe callback is executed by the main thread, too. Any expensive processing inside this callback will freeze your UI!

like image 189
user1185087 Avatar answered Nov 14 '22 18:11

user1185087


In another solution, you can call it from main dispatcher as

GlobalScope.launch(Dispatchers.Main) {
  // your code here...
}
like image 31
Ercan Avatar answered Nov 14 '22 18:11

Ercan


Additionally to the nice and detailled answer from @user1185087 here is a solution if you're using RxJava in your project. It's maybe not that short, but if you already use RxJava in your project, it's an elegant way to switch to the required thread (in this case the Android's UI thread via .observeOn(AndroidSchedulers.mainThread())).

Observable.just(workManager.getStatusById(workRequest.getId()))
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(status -> status.observeForever(workStatus -> {
        // Handling result on UI thread
    }), err -> Log.e(TAG, err.getMessage(), err));
like image 1
Danny Avatar answered Nov 14 '22 18:11

Danny


In my case I was testing liveData and I forgot to add the InstantTaskExecutorRule().

    @RunWith(AndroidJUnit4::class)
    class UserDaoTest {
        @get:Rule // <----
        var instantExecutorRule = InstantTaskExecutorRule() // <----
        ....
    }

Don't forget to add the library to the project.

    testImplementation"androidx.arch.core:core-testing:2.1.0" // unit tests
    androidTestImplementation "androidx.arch.core:core-testing:2.1.0"//instrumentation tests
like image 1
Bacar Pereira Avatar answered Nov 14 '22 19:11

Bacar Pereira