Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use NetworkBoundResource with suspend fun in Retorfit

I am using Retrofit, LiveData, Room (Android AAC). NetworkBoundResource is a great helper to combine network source and room provided by googlesimple.

Since Retrofit 2.6.0 introduced built-in support for suspend, I tried to modify NetworkBoundResource to use suspend funciton instaed of LiveDataCallAdapter but encountered a lot of troubles.

Here's my modification:

abstract class NetworkBondResource<ResultType, RequestType>
@MainThread constructor() {
    private val result = MediatorLiveData<Resource<ResultType>>()

    private val supervisorJob = SupervisorJob()

    init {
        result.value = Resource.loading(null)
    }

    fun asLiveData() = result as LiveData<Resource<ResultType>>

    suspend fun load() {
        withContext(Dispatchers.Main) {
            val dbSource = loadFromDb()
            result.addSource(dbSource) { data ->
                result.removeSource(dbSource)
                if (shouldFetch(data)) {
                    // ! HERE--------------
                    GlobalScope.launch(Dispatchers.IO) {
                        fetchFromNetwork(dbSource)
                    }
                } else {
                    result.addSource(dbSource) { newData ->
                        setValue(Resource.success(newData))
                    }
                }
            }
        }
    }

    // others code...
}

The question is code within result.addSource(dbSource) unable to inherit external scope. I have to use GlobalScope to launch a new coroutine which will causing a 'Coroutine Leak' since it is not controlled by the viewModel scope.

Also I found another way. But this scheme violates the principle of a single trusted source, lost the core role of NetworkBoundResource.

Any ideas will be appreciated.

like image 564
Chenhe Avatar asked Jul 12 '19 08:07

Chenhe


Video Answer


1 Answers

I also had the same problem, my solution was this. I recommend that you do not use GlobalScope for these reasons. Make it clear that result.addSoruce can only be declared within the main thread. I hope it helps you, if there is a better solution please let me know.

 private val result = MediatorLiveData<Resource<ResultType>>()
private val supervisorJob = SupervisorJob()

suspend fun load(): NetworkBoundResource<ResultType, RequestType> {

    val context = Dispatchers.IO
    context + supervisorJob


    withContext(Dispatchers.Main) {
            result.value = Resource.loading(null)
            val dbResult = loadFromDb()
            result.addSource(dbResult){data->
                result.removeSource(dbResult)
                if (shouldFetch(data)){
                   try {
                       CoroutineScope(context).launch {
                           fetchFromNetwork(dbResult)
                       }
                   }catch (e:Exception){
                       Timber.i("NetworkBoundResource: An error happened: $e")
                       result.addSource(dbResult){newData->
                           setValue(Resource.error(e.message!!, newData))
                       }
                   }
                }else{
                    Timber.i("NetworkBoundResource: Return data from local database")
                    result.addSource(dbResult){newData->
                        setValue(Resource.success(newData))
                    }

                }
            }

    }

    return this

}
like image 94
Yonatan Segura Avatar answered Oct 13 '22 06:10

Yonatan Segura