web-dev-qa-db-fra.com

java.lang.IllegalStateException: impossible d'appeler observeForever sur un thread d'arrière-plan

Quelqu'un peut-il m'aider à trouver où je me trompe ici. J'ai besoin d'observer continuellement les données du réseau et de mettre à jour l'interface utilisateur chaque fois qu'il y a un changement de données du travailleur. Veuillez noter que cela fonctionnait avant la mise à niveau vers androidx.

Voici une classe ouvrière.

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"
    }

}

Qui s'appelle comme suit:

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()
}

J'ai également un UserDao et UserRepository pour récupérer et stocker des données. J'observe les données réseau dans UserRepository comme suit:

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)
        }
    }}

Quelqu'un peut-il m'aider à localiser où je me trompe? Cela me donne une erreur comme suit:

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)
8
FreddCha

Change ça:

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

Pour ça:

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

Explication

Normalement, observe(..) et observeForever(..) doivent être appelés à partir du thread principal car leurs rappels (Observer<T>.onChanged(T t)) changent souvent l'interface utilisateur, ce qui n'est possible que dans le thread principal. C'est la raison pour laquelle Android vérifie si l'appel des fonctions d'observation est effectué par le thread principal. Sinon une exception est levée (IllegalStateException: Cannot invoke observeForever on a background thread). Dans votre cas UserRepository.init{} Est appelé par un thread d'arrière-plan, donc l'exception est levée. Pour revenir au thread principal, vous pouvez publier un Runnable au thread principal en appelant Handler(Looper.getMainLooper()).post {}. Mais attention le code à l'intérieur de votre rappel observe est également exécuté par le thread principal. Tout traitement coûteux à l'intérieur de ce rappel gèlera votre interface utilisateur!

5
user1185087

Dans une autre solution, vous pouvez l'appeler depuis le répartiteur principal en tant que

GlobalScope.launch(Dispatchers.Main) {
  // your code here...
}
4
Ercan

En plus de la réponse agréable et détaillée de @ user1185087, voici une solution si vous utilisez RxJava dans votre projet. Ce n'est peut-être pas si court, mais si vous utilisez déjà RxJava dans votre projet, c'est un moyen élégant de basculer vers le thread requis (dans ce cas, le thread d'interface utilisateur d'Android 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));
1
heisenberg