Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

State machine for pagination logic #27

Open
Alex009 opened this issue Mar 11, 2021 · 0 comments
Open

State machine for pagination logic #27

Alex009 opened this issue Mar 11, 2021 · 0 comments
Labels
enhancement New feature or request

Comments

@Alex009
Copy link
Member

Alex009 commented Mar 11, 2021

Here some research with states without boolean livedatas:

sealed class NewsItem {

    data class Short(
        override val id: Int,
        override val title: String
    ) : NewsItem()

    data class Detailed(
        override val id: Int,
        override val title: String,
        val text: String
    ) : NewsItem()

    abstract val id: Int
    abstract val title: String
}

interface NewsApi {
    suspend fun loadPage(page: Int, size: Int): List<NewsItem.Short>
    suspend fun loadDetails(id: Int): NewsItem.Detailed
}

class NewsRepository(
    private val api: NewsApi
) {
    private val _newsState = MutableStateFlow<ResourceStateThrow<PagingDataState<NewsItem>>>(
        value = ResourceState.Empty()
    )
    val newsState: StateFlow<ResourceStateThrow<PagingDataState<NewsItem>>> get() = _newsState

    suspend fun loadFirstPage() {
        val state = _newsState.value
        if (state !is ResourceState.Empty && state !is ResourceState.Error) return

        _newsState.value = ResourceState.Loading()
        _newsState.value = try {
            val firstPage = api.loadPage(0, PAGE_SIZE)
            val dataState = PagingDataState.Normal<NewsItem>(firstPage)
            ResourceState.Data(data = dataState)
        } catch (exc: Exception) {
            ResourceState.Error(exc)
        }
    }

    suspend fun loadNextPage() {
        val state = _newsState.value
        if (state !is ResourceState.Data) return
        if (state.data !is PagingDataState.Normal) return

        val currentItems = state.data.items
        val loadingState = PagingDataState.LoadNextPage(currentItems)
        _newsState.value = ResourceState.Data(loadingState)

        val nextPageIndex = (currentItems.size / PAGE_SIZE) + 1
        val nextPageItems = api.loadPage(nextPageIndex, PAGE_SIZE)

        val itemsToAdd = nextPageItems.filter { currentItems.contains(it).not() }

        val newState = PagingDataState.Normal(currentItems + itemsToAdd)
        _newsState.value = ResourceState.Data(newState)
    }

    suspend fun refreshData() {
        val state = _newsState.value
        if (state !is ResourceState.Data) return
        if (state.data !is PagingDataState.Normal) return

        val currentItems = state.data.items
        val loadingState = PagingDataState.Refresh(currentItems)
        _newsState.value = ResourceState.Data(loadingState)

        val updatedFirstPage = api.loadPage(0, PAGE_SIZE)

        val newState = PagingDataState.Normal<NewsItem>(updatedFirstPage)
        _newsState.value = ResourceState.Data(newState)
    }

    suspend fun loadDetails(id: Int): NewsItem.Detailed {
        val detailed = api.loadDetails(id)

        val currentState = _newsState.value
        if (currentState is ResourceState.Data && currentState.data is PagingDataState.Normal) {
            val items = currentState.data.items
            val updatedNews = items.map {
                if (it.id == id) detailed
                else it
            }
            _newsState.value = ResourceState.Data(PagingDataState.Normal(updatedNews))
        } else {
            throw IllegalStateException("try to update details item while in not normal data state")
        }

        return detailed
    }

    private companion object {
        const val PAGE_SIZE = 10
    }
}

typealias ResourceStateThrow<T> = ResourceState<T, Throwable>

sealed class ResourceState<T, E> {
    class Empty<T, E> : ResourceState<T, E>()
    class Loading<T, E> : ResourceState<T, E>()
    data class Data<T, E>(val data: T) : ResourceState<T, E>()
    data class Error<T, E>(val error: E) : ResourceState<T, E>()
}

sealed class PagingDataState<T> {
    data class Normal<T>(override val items: List<T>) : PagingDataState<T>()
    data class Refresh<T>(override val items: List<T>) : PagingDataState<T>()
    data class LoadNextPage<T>(override val items: List<T>) : PagingDataState<T>()

    abstract val items: List<T>
}
@Alex009 Alex009 added the enhancement New feature or request label Mar 11, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Status: No status
Development

No branches or pull requests

1 participant