Skip to content
This repository has been archived by the owner on Nov 12, 2024. It is now read-only.

Add retained caching for paging flows #1763

Merged
merged 1 commit into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
package app.tivi.common.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import app.cash.paging.CombinedLoadStates
import app.cash.paging.LoadStateError
import app.cash.paging.PagingData
import app.cash.paging.cachedIn
import com.slack.circuit.retained.rememberRetained
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow

Expand All @@ -30,6 +30,6 @@ fun CombinedLoadStates.refreshErrorOrNull(): UiMessage? {
}

@Composable
inline fun <T : Any> Flow<PagingData<T>>.rememberCachedPagingFlow(
scope: CoroutineScope = rememberCoroutineScope(),
): Flow<PagingData<T>> = remember(this, scope) { cachedIn(scope) }
inline fun <T : Any> Flow<PagingData<T>>.rememberRetainedCachedPagingFlow(
scope: CoroutineScope = rememberRetainedCoroutineScope(),
): Flow<PagingData<T>> = rememberRetained(this, scope) { cachedIn(scope) }
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@
package app.tivi.common.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.RememberObserver
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import com.slack.circuit.retained.rememberRetained
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel

/**
* Returns a [StableCoroutineScope] around a [androidx.compose.runtime.rememberCoroutineScope].
Expand All @@ -22,3 +27,20 @@ fun rememberCoroutineScope(): StableCoroutineScope {
/** @see rememberCoroutineScope */
@Stable
class StableCoroutineScope(scope: CoroutineScope) : CoroutineScope by scope

@Composable
fun rememberRetainedCoroutineScope(): StableCoroutineScope {
return rememberRetained("coroutine_scope") {
object : RememberObserver {
val scope = StableCoroutineScope(CoroutineScope(Dispatchers.Main + Job()))

override fun onAbandoned() = onForgotten()

override fun onForgotten() {
scope.cancel()
}

override fun onRemembered() = Unit
}
}.scope
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import app.cash.paging.PagingConfig
import app.cash.paging.compose.collectAsLazyPagingItems
import app.tivi.common.compose.UiMessage
import app.tivi.common.compose.UiMessageManager
import app.tivi.common.compose.rememberCachedPagingFlow
import app.tivi.common.compose.rememberCoroutineScope
import app.tivi.common.compose.rememberRetainedCachedPagingFlow
import app.tivi.data.models.SortOption
import app.tivi.data.traktauth.TraktAuthState
import app.tivi.domain.interactors.GetTraktAuthState
Expand All @@ -31,6 +31,7 @@ import app.tivi.settings.TiviPreferences
import app.tivi.util.Logger
import app.tivi.util.onException
import com.slack.circuit.retained.collectAsRetainedState
import com.slack.circuit.retained.rememberRetained
import com.slack.circuit.runtime.CircuitContext
import com.slack.circuit.runtime.Navigator
import com.slack.circuit.runtime.presenter.Presenter
Expand Down Expand Up @@ -71,8 +72,13 @@ class LibraryPresenter(
val scope = rememberCoroutineScope()
val uiMessageManager = remember { UiMessageManager() }

val items = observePagedLibraryShows.value.flow
.rememberCachedPagingFlow(scope)
// Yes, this is gross. We need the same flow instance across Presenter instances. We could
// make the interactor have @ApplicationScope, but that has other consequences if we use the
// same interactor at the same time across UIs. Instead we just retain the instance
val retainedObservePagedLibraryShows = rememberRetained { observePagedLibraryShows.value }

val items = retainedObservePagedLibraryShows.flow
.rememberRetainedCachedPagingFlow()
.collectAsLazyPagingItems()

var filter by remember { mutableStateOf<String?>(null) }
Expand Down Expand Up @@ -144,7 +150,7 @@ class LibraryPresenter(

LaunchedEffect(filter, sort, includeFollowedShows, includeWatchedShows) {
// When the filter and sort options change, update the data source
observePagedLibraryShows.value.invoke(
retainedObservePagedLibraryShows(
ObservePagedLibraryShows.Parameters(
sort = sort,
filter = filter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import app.cash.paging.PagingConfig
import app.cash.paging.compose.collectAsLazyPagingItems
import app.tivi.common.compose.rememberCachedPagingFlow
import app.tivi.common.compose.rememberRetainedCachedPagingFlow
import app.tivi.domain.observers.ObservePagedPopularShows
import app.tivi.screens.PopularShowsScreen
import app.tivi.screens.ShowDetailsScreen
import com.slack.circuit.retained.rememberRetained
import com.slack.circuit.runtime.CircuitContext
import com.slack.circuit.runtime.Navigator
import com.slack.circuit.runtime.presenter.Presenter
Expand Down Expand Up @@ -40,12 +41,17 @@ class PopularShowsPresenter(

@Composable
override fun present(): PopularShowsUiState {
val items = pagingInteractor.value.flow
.rememberCachedPagingFlow()
// Yes, this is gross. We need the same flow instance across Presenter instances. We could
// make the interactor have @ApplicationScope, but that has other consequences if we use the
// same interactor at the same time across UIs. Instead we just retain the instance
val retainedPagingInteractor = rememberRetained { pagingInteractor.value }

val items = retainedPagingInteractor.flow
.rememberRetainedCachedPagingFlow()
.collectAsLazyPagingItems()

LaunchedEffect(Unit) {
pagingInteractor.value.invoke(ObservePagedPopularShows.Params(PAGING_CONFIG))
retainedPagingInteractor(ObservePagedPopularShows.Params(PAGING_CONFIG))
}

fun eventSink(event: PopularShowsUiEvent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import app.cash.paging.PagingConfig
import app.cash.paging.compose.collectAsLazyPagingItems
import app.tivi.common.compose.rememberCachedPagingFlow
import app.tivi.common.compose.rememberRetainedCachedPagingFlow
import app.tivi.domain.observers.ObservePagedRecommendedShows
import app.tivi.screens.RecommendedShowsScreen
import app.tivi.screens.ShowDetailsScreen
import com.slack.circuit.retained.rememberRetained
import com.slack.circuit.runtime.CircuitContext
import com.slack.circuit.runtime.Navigator
import com.slack.circuit.runtime.presenter.Presenter
Expand Down Expand Up @@ -40,12 +41,17 @@ class RecommendedShowsPresenter(

@Composable
override fun present(): RecommendedShowsUiState {
val items = pagingInteractor.value.flow
.rememberCachedPagingFlow()
// Yes, this is gross. We need the same flow instance across Presenter instances. We could
// make the interactor have @ApplicationScope, but that has other consequences if we use the
// same interactor at the same time across UIs. Instead we just retain the instance
val retainedPagingInteractor = rememberRetained { pagingInteractor.value }

val items = retainedPagingInteractor.flow
.rememberRetainedCachedPagingFlow()
.collectAsLazyPagingItems()

LaunchedEffect(Unit) {
pagingInteractor.value.invoke(ObservePagedRecommendedShows.Params(PAGING_CONFIG))
retainedPagingInteractor(ObservePagedRecommendedShows.Params(PAGING_CONFIG))
}

fun eventSink(event: RecommendedShowsUiEvent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import app.cash.paging.PagingConfig
import app.cash.paging.compose.collectAsLazyPagingItems
import app.tivi.common.compose.rememberCachedPagingFlow
import app.tivi.common.compose.rememberRetainedCachedPagingFlow
import app.tivi.domain.observers.ObservePagedTrendingShows
import app.tivi.screens.ShowDetailsScreen
import app.tivi.screens.TrendingShowsScreen
import com.slack.circuit.retained.rememberRetained
import com.slack.circuit.runtime.CircuitContext
import com.slack.circuit.runtime.Navigator
import com.slack.circuit.runtime.presenter.Presenter
Expand Down Expand Up @@ -40,12 +41,17 @@ class TrendingShowsPresenter(

@Composable
override fun present(): TrendingShowsUiState {
val items = pagingInteractor.value.flow
.rememberCachedPagingFlow()
// Yes, this is gross. We need the same flow instance across Presenter instances. We could
// make the interactor have @ApplicationScope, but that has other consequences if we use the
// same interactor at the same time across UIs. Instead we just retain the instance
val retainedPagingInteractor = rememberRetained { pagingInteractor.value }

val items = retainedPagingInteractor.flow
.rememberRetainedCachedPagingFlow()
.collectAsLazyPagingItems()

LaunchedEffect(Unit) {
pagingInteractor.value.invoke(ObservePagedTrendingShows.Params(PAGING_CONFIG))
retainedPagingInteractor(ObservePagedTrendingShows.Params(PAGING_CONFIG))
}

fun eventSink(event: TrendingShowsUiEvent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import app.cash.paging.PagingConfig
import app.cash.paging.compose.collectAsLazyPagingItems
import app.tivi.common.compose.UiMessage
import app.tivi.common.compose.UiMessageManager
import app.tivi.common.compose.rememberCachedPagingFlow
import app.tivi.common.compose.rememberCoroutineScope
import app.tivi.common.compose.rememberRetainedCachedPagingFlow
import app.tivi.data.models.SortOption
import app.tivi.data.traktauth.TraktAuthState
import app.tivi.domain.interactors.GetTraktAuthState
Expand All @@ -30,6 +30,7 @@ import app.tivi.settings.TiviPreferences
import app.tivi.util.Logger
import app.tivi.util.onException
import com.slack.circuit.retained.collectAsRetainedState
import com.slack.circuit.retained.rememberRetained
import com.slack.circuit.runtime.CircuitContext
import com.slack.circuit.runtime.Navigator
import com.slack.circuit.runtime.presenter.Presenter
Expand Down Expand Up @@ -71,8 +72,13 @@ class UpNextPresenter(

val uiMessageManager = remember { UiMessageManager() }

val items = observePagedUpNextShows.value.flow
.rememberCachedPagingFlow(scope)
// Yes, this is gross. We need the same flow instance across Presenter instances. We could
// make the interactor have @ApplicationScope, but that has other consequences if we use the
// same interactor at the same time across UIs. Instead we just retain the instance
val retainedObservePagedUpNextShows = rememberRetained { observePagedUpNextShows.value }

val items = retainedObservePagedUpNextShows.flow
.rememberRetainedCachedPagingFlow()
.collectAsLazyPagingItems()

var sort by remember { mutableStateOf(SortOption.LAST_WATCHED) }
Expand Down Expand Up @@ -131,7 +137,7 @@ class UpNextPresenter(

LaunchedEffect(sort, followedShowsOnly) {
// When the filter and sort options change, update the data source
observePagedUpNextShows.value.invoke(
retainedObservePagedUpNextShows(
ObservePagedUpNextShows.Parameters(
sort = sort,
followedOnly = followedShowsOnly,
Expand Down
Loading