2

I saw this but I'm not sure how to implement it or if this is the same issue, I have a mediator live data that updates when either of its 2 source live datas update or when the underlying data (Room db) updates, it seems to work fine but if the data updates a lot it refreshes a lot in quick succession and I get an error

Cannot run invalidation tracker. Is the db closed?
Cannot access database on the main thread since it may potentially lock the UI for a long period of time

this doesn't happen everytime, only when there are a lot of updates to the database in very quick succession heres the problem part of the view model,

var search: MutableLiveData<String> = getSearchState()
val filters: MutableLiveData<MutableSet<String>> = getCurrentFiltersState()
val searchPokemon: LiveData<PagingData<PokemonWithTypesAndSpeciesForList>>

val isFiltersLayoutExpanded: MutableLiveData<Boolean> = getFiltersLayoutExpanded()

init {

    val combinedValues =
        MediatorLiveData<Pair<String?, MutableSet<String>?>?>().apply {
            addSource(search) {
                value = Pair(it, filters.value)
            }
            addSource(filters) {
                value = Pair(search.value, it)
            }
        }

    searchPokemon = Transformations.switchMap(combinedValues) { pair ->
        val search = pair?.first
        val filters = pair?.second
        if (search != null && filters != null) {
            searchAndFilterPokemonPager(search, filters.toList())
        } else null
    }.distinctUntilChanged()
}

@SuppressLint("DefaultLocale")
private fun searchAndFilterPokemonPager(search: String, filters: List<String>): LiveData<PagingData<PokemonWithTypesAndSpeciesForList>> {
    return Pager(
        config = PagingConfig(
            pageSize = 20,
            enablePlaceholders = false,
            maxSize = 60
        )
    ) {
        if (filters.isEmpty()){
            searchPokemonForPaging(search)
        } else {
            searchAndFilterPokemonForPaging(search, filters)
        }
    }.liveData.cachedIn(viewModelScope)
}

@SuppressLint("DefaultLocale")
private fun getAllPokemonForPaging(): PagingSource<Int, PokemonWithTypesAndSpecies> {
    return repository.getAllPokemonWithTypesAndSpeciesForPaging()
}

@SuppressLint("DefaultLocale")
private fun searchPokemonForPaging(search: String): PagingSource<Int, PokemonWithTypesAndSpeciesForList> {
    return repository.searchPokemonWithTypesAndSpeciesForPaging(search)
}

@SuppressLint("DefaultLocale")
private fun searchAndFilterPokemonForPaging(search: String, filters: List<String>): PagingSource<Int, PokemonWithTypesAndSpeciesForList> {
    return repository.searchAndFilterPokemonWithTypesAndSpeciesForPaging(search, filters)
}

the error is actually thrown from the function searchPokemonForPaging

for instance it happens when the app starts which does about 300 writes but if I force the calls off the main thread by making everything suspend and use runBlocking to return the Pager it does work and I don't get the error anymore but it obviously blocks the ui, so is there a way to maybe make the switchmap asynchronous or make the searchAndFilterPokemonPager method return a pager asynchronously? i know the second is technically possible (return from async) but maybe there is a way for coroutines to solve this
thanks for any help

martinseal1987
  • 1,862
  • 8
  • 44
  • 77

1 Answers1

2

You can simplify combining using combineTuple (which is available as a library that I wrote for this specific purpose) (optional)

Afterwards, you can use the liveData { coroutine builder to move execution to background thread

Now your code will look like

val search: MutableLiveData<String> = getSearchState()
val filters: MutableLiveData<Set<String>> = getCurrentFiltersState()
val searchPokemon: LiveData<PagingData<PokemonWithTypesAndSpeciesForList>>

val isFiltersLayoutExpanded: MutableLiveData<Boolean> = getFiltersLayoutExpanded()

init {
    searchPokemon = combineTuple(search, filters).switchMap { (search, filters) -> 
        liveData {
            val search = search ?: return@liveData
            val filters = filters ?: return@liveData

            withContext(Dispatchers.IO) {
                emit(searchAndFilterPokemonPager(search, filters.toList()))
            }
        }
    }.distinctUntilChanged()
}
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • your library looks great, ive no idea who downvoted this answer, im trying to implement it but without the library but without any success – martinseal1987 Jan 04 '21 at 17:13
  • im trying to do this but i have an error that im putting a livedata inside a livedata searchPokemon = Transformations.switchMap(combinedValues) { pair -> liveData { withContext(Dispatchers.IO) { emit(searchAndFilterPokemonPager(xSearch, xFilters)) } }.distinctUntilChanged() } – martinseal1987 Jan 04 '21 at 17:16
  • ok i understand the error that i have a live data inside a live data but not sure how to fix it as the pager has to be returned as a livedata or a flow maybe i could join it instead of wrapping it – martinseal1987 Jan 04 '21 at 17:25
  • ok thats what the switchmap is doing on your combine tuple, so i still need Transformations.switchmap? – martinseal1987 Jan 04 '21 at 17:26
  • ive added an answer to show what i have but i still have errors being thrown – martinseal1987 Jan 04 '21 at 18:06
  • I think you will need to make the functions you call from `Pager {}` to be `suspend fun` so that it can be executed on a background thread. – EpicPandaForce Jan 05 '21 at 16:49
  • Ive added the methods from the repo and dao i believe it is already on a background thread because of how Room works, but if i were to explicitly move them im not sure how I'd return it to the pager method without running blocking? – martinseal1987 Jan 05 '21 at 19:34