Coroutine Flow sudden cancellation with Retrofit2

59 views Asked by At

In our fragment we have this code inside onViewCreated.

    with(viewModel) {
        viewLifecycleOwner.collectShared(newsState, ::onNewsStateChanged)
        // Initial fetch
        fetchNews(
            "some_data",
            false
        )

        swipeRefreshLayout.setOnRefreshListener {
            // Reload data
            viewModel.fetchNews("some_data", true)
        }
    }

An extension function

inline fun <T : Any, L : SharedFlow<T>> LifecycleOwner.collectShared(
    sharedFlow: L,
    crossinline function: (T) -> Unit,
    lifecycleState: Lifecycle.State = Lifecycle.State.STARTED
) {
    lifecycleScope.launch {
        repeatOnLifecycle(lifecycleState) {
            sharedFlow.collect { t -> function(t) }
        }
    }
}

fun <T : Any> defaultMutableSharedFlow() =
    MutableSharedFlow<T>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)

ViewModel

private val _newsState = defaultMutableSharedFlow<NewsState>()

val newsState = _newsState.asSharedFlow()

fun fetchNews(category: String, isReload: Boolean) {

    viewModelScope.launch {

        getNewsUseCase(
            isReload,
            AppConfig.remote.newsLink,
            AppConfig.remote.newsField,
            category,
            if (isReload)
                getNewsUseCase.getPageNumber().minus(1)
            else
                getNewsUseCase.getPageNumber(),
        ).onEach {

            when (it) {

                is RequestStatus.Loading -> {
                    _newsState.tryEmit(NewsState.FetchLoading(it.data))
                }

                is RequestStatus.Success -> {
                    _newsState.tryEmit(NewsState.FetchSuccess(it.data))
                }

                is RequestStatus.Canceled -> {
                    // No operation is needed
                }

                is RequestStatus.Failed -> {
                    _newsState.tryEmit(NewsState.FetchFailed(it.message, it.data))
                }

            }

        }.collect()

        _lastSelectedCategory = category

    }

}

Repository

override fun fetchNews(
    isReload: Boolean,
    baseUrl: String,
    query: String,
    category: String,
    page: Int
) = flow {

    emit(RequestStatus.Loading())

    var domainModel = mapper.mapToDomainModelList(dao.getNewsByCategoryId(category, page))

    try {

        val cacheList = getProcessedNewsList(domainModel)

        // Fetching data from remote can take longer
        // Showing the cache first if available
        emit(RequestStatus.Loading(cacheList))

        val dtoModel = service.getNewsItems(
            baseUrl + "posts",
            query,
            category,
            page.toString()
        )

        val entities = mapper.mapToEntityModelList(dtoModel)
        entities.forEach {
            it.category = category
        }

        // Updating cache
        dao.insert(entities)

        // Get the updated cache
        domainModel = mapper.mapToDomainModelList(dao.getNewsByCategoryId(category, page))

        val freshData = getProcessedNewsList(domainModel)

        // New page was fetched, if API response returns empty list then retain the page number
        if (domainModel.isNotEmpty() && isReload.not())
            _pageNumber = _pageNumber.plus(1)

        emit(RequestStatus.Success(freshData))

    } catch (e: HttpException) {

        val cacheDomainList = getProcessedNewsList(domainModel)

        // Support pagination for offline
        if (domainModel.isNotEmpty() && isReload.not())
            _pageNumber = _pageNumber.plus(1)

        emit(RequestStatus.Failed(e, cacheDomainList))

    } catch (e: IOException) {

        val cacheDomainList = getProcessedNewsList(domainModel)

        // Support pagination for offline
        if (domainModel.isNotEmpty() && isReload.not())
            _pageNumber = _pageNumber.plus(1)

        emit(RequestStatus.Failed(e, cacheDomainList))

    }

}

We are sometimes getting <-- HTTP FAILED: java.io.IOException: Canceled specially when triggering the SwipeRefreshLayout. As far as we know, this happens when the request or subscriber if using RxJava was cancelled but we are do not have code that performs cancelling here and leaving all handling to LifeCycle component. It seems the Coroutine is being cancelled. What is the issue here and how to avoid the cancellation?

0

There are 0 answers