Skip to content

Commit

Permalink
refactor: change cache policy during manual refresh (#339)
Browse files Browse the repository at this point in the history
  • Loading branch information
cbeyls authored Mar 1, 2025
1 parent e937de4 commit fe13c55
Show file tree
Hide file tree
Showing 17 changed files with 98 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class FakeAndroidMakersStore : RoomsRepository, VenueRepository, SpeakersReposit
SessionsRepository, PartnersRepository {
val roomsMutableFlow = MutableStateFlow(Result.success(emptyList<Room>()))

override fun getRooms(): Flow<Result<List<Room>>> = roomsMutableFlow
override fun getRooms(refresh: Boolean): Flow<Result<List<Room>>> = roomsMutableFlow

override fun getVenue(id: String): Flow<Result<Venue>> {
TODO("Not yet implemented")
Expand All @@ -39,11 +39,11 @@ class FakeAndroidMakersStore : RoomsRepository, VenueRepository, SpeakersReposit
TODO("Not yet implemented")
}

override fun getSessions(): Flow<Result<List<Session>>> {
override fun getSessions(refresh: Boolean): Flow<Result<List<Session>>> {
TODO("Not yet implemented")
}

override fun getSpeakers(): Flow<Result<List<Speaker>>> {
override fun getSpeakers(refresh: Boolean): Flow<Result<List<Speaker>>> {
TODO("Not yet implemented")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ internal fun <T : Operation.Data> Flow<ApolloResponse<T>>.ignoreCacheMisses(): F
}


internal fun <T : Operation.Data> ApolloCall<T>.cacheAndNetwork(): Flow<Result<T>> {
internal fun <T : Operation.Data> ApolloCall<T>.cacheAndNetwork(
refresh: Boolean = false
): Flow<Result<T>> {
return flow {
var hasData = false
var exception: ApolloException? = null
fetchPolicy(FetchPolicy.CacheAndNetwork)
fetchPolicy(if (refresh) FetchPolicy.NetworkFirst else FetchPolicy.CacheAndNetwork)
.toFlow()
.collect {
val data = it.data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ class PartnersGraphQLRepository(private val apolloClient: ApolloClient): Partner
override fun getPartners(): Flow<Result<List<PartnerGroup>>> {
return apolloClient.query(GetPartnerGroupsQuery())
.cacheAndNetwork()
.map {
it.map {
it.partnerGroups.map { partnerGroup ->
.map { dataResult ->
dataResult.map { data ->
data.partnerGroups.map { partnerGroup ->
PartnerGroup(
title = partnerGroup.title,
partners = partnerGroup.partners.map { partner ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,25 @@ import fr.androidmakers.domain.repo.RoomsRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

class RoomsGraphQLRepository(private val apolloClient: ApolloClient): RoomsRepository {
class RoomsGraphQLRepository(private val apolloClient: ApolloClient) : RoomsRepository {
override fun getRoom(id: String): Flow<Result<Room>> {
return getRooms().map {
it.map { it.singleOrNull { it.id == id } ?: error("Not Room") }
}
return apolloClient.query(GetRoomsQuery())
.cacheAndNetwork()
.map { dataResult ->
dataResult.mapCatching { data ->
data.rooms.firstOrNull { it.roomDetails.id == id }?.roomDetails?.toRoom()
?: error("Room not found")
}
}
}

override fun getRooms(): Flow<Result<List<Room>>> {
override fun getRooms(refresh: Boolean): Flow<Result<List<Room>>> {
return apolloClient.query(GetRoomsQuery())
.cacheAndNetwork()
.map { it.map { it.rooms.map { it.roomDetails.toRoom() } } }
.cacheAndNetwork(refresh)
.map { dataResult ->
dataResult.map { data ->
data.rooms.map { it.roomDetails.toRoom() }
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,30 @@ class SessionsGraphQLRepository(private val apolloClient: ApolloClient) : Sessio
override fun getSession(id: String): Flow<Result<Session>> {
return apolloClient.query(GetSessionQuery(id))
.cacheAndNetwork()
.map { it.map { it.session.sessionDetails.toSession() } }
.map { dataResult ->
dataResult.map { data ->
data.session.sessionDetails.toSession()
}
}
}

override fun getBookmarks(uid: String): Flow<Result<Set<String>>> {
return apolloClient.query(BookmarksQuery())
.fetchPolicy(FetchPolicy.NetworkOnly)
.toFlow()
.map {
it.dataAssertNoErrors.bookmarkConnection.nodes.map { it.id }.toSet()
.map { response ->
response.dataAssertNoErrors.bookmarkConnection.nodes.map { it.id }.toSet()
}
.toResultFlow()
}

override fun getSessions(): Flow<Result<List<Session>>> {
override fun getSessions(refresh: Boolean): Flow<Result<List<Session>>> {
return apolloClient.query(GetSessionsQuery())
.cacheAndNetwork()
.map { it.map { it.sessions.nodes.map { it.sessionDetails.toSession() } } }
.cacheAndNetwork(refresh)
.map { dataResult ->
dataResult.map { data ->
data.sessions.nodes.map { it.sessionDetails.toSession() }
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,37 +1,33 @@
package fr.androidmakers.store.graphql

import com.apollographql.apollo.ApolloClient
import com.apollographql.apollo.exception.DefaultApolloException
import fr.androidmakers.domain.model.Speaker
import fr.androidmakers.domain.repo.SpeakersRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

class SpeakersGraphQLRepository(private val apolloClient: ApolloClient) : SpeakersRepository {

override fun getSpeakers(): Flow<Result<List<Speaker>>> {
override fun getSpeakers(refresh: Boolean): Flow<Result<List<Speaker>>> {
return apolloClient.query(GetSpeakersQuery())
.cacheAndNetwork()
.map { it.map { it.speakers.map { it.speakerDetails.toSpeaker() } } }
.cacheAndNetwork(refresh)
.map { dataResult ->
dataResult.map { data ->
data.speakers.map {
it.speakerDetails.toSpeaker()
}
}
}
}

override fun getSpeaker(id: String): Flow<Result<Speaker>> {
return apolloClient.query(GetSpeakersQuery())
.cacheAndNetwork()
.map {
it.fold(
onSuccess = { value ->
val speaker = value.speakers.map { it.speakerDetails }.singleOrNull { it.id == id }?.toSpeaker()
if (speaker != null) {
Result.success(speaker)
} else {
Result.failure(DefaultApolloException("Something wrong happened"))
}
},
onFailure = { exception ->
Result.failure(exception)
}
)
.map { dataResult ->
dataResult.mapCatching { data ->
data.speakers.firstOrNull { it.speakerDetails.id == id }?.speakerDetails?.toSpeaker()
?: error("Speaker not found")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ import fr.androidmakers.domain.repo.VenueRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

class VenueGraphQLRepository(private val apolloClient: ApolloClient): VenueRepository {
class VenueGraphQLRepository(private val apolloClient: ApolloClient) : VenueRepository {
override fun getVenue(id: String): Flow<Result<Venue>> {
return apolloClient.query(GetVenueQuery(id))
.cacheAndNetwork()
.map { it.map { it.venue.toVenue() } }
.cacheAndNetwork()
.map { dataResult ->
dataResult.map { data ->
data.venue.toVenue()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ class GetAgendaUseCase(
private val speakersRepository: SpeakersRepository,
private val roomsRepository: RoomsRepository,
) {
operator fun invoke(): Flow<Result<Agenda>> {
operator fun invoke(refresh: Boolean): Flow<Result<Agenda>> {
return combine(
sessionsRepository.getSessions(),
roomsRepository.getRooms(),
speakersRepository.getSpeakers(),
sessionsRepository.getSessions(refresh),
roomsRepository.getRooms(refresh),
speakersRepository.getSpeakers(refresh),
) { sessionsResult, roomsResult, speakersResult ->

val sessions = sessionsResult.getOrElse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ interface RoomsRepository {

fun getRoom(id: String): Flow<Result<Room>>

fun getRooms(): Flow<Result<List<Room>>>
fun getRooms(refresh: Boolean): Flow<Result<List<Room>>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.Flow
interface SessionsRepository {
fun getSession(id: String): Flow<Result<Session>>

fun getSessions(): Flow<Result<List<Session>>>
fun getSessions(refresh: Boolean): Flow<Result<List<Session>>>

fun getBookmarks(userId: String): Flow<Result<Set<String>>>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ interface SpeakersRepository {

fun getSpeaker(id: String): Flow<Result<Speaker>>

fun getSpeakers(): Flow<Result<List<Speaker>>>
fun getSpeakers(refresh: Boolean): Flow<Result<List<Speaker>>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ fun AgendaLayout(
drawerState = agendaFilterDrawerState,
drawerContent = {
ModalDrawerSheet(
drawerContainerColor = MaterialTheme.colorScheme.background,
drawerContentColor = MaterialTheme.colorScheme.onBackground,
drawerContainerColor = MaterialTheme.colorScheme.surface,
drawerContentColor = MaterialTheme.colorScheme.onSurface,
drawerShape = RectangleShape,
windowInsets = WindowInsets(0, 0, 0, 0),
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class AgendaLayoutViewModel : ViewModel {
private fun createState(
roomsRepository: RoomsRepository
): StateFlow<AgendaLayoutState> = combine(
roomsRepository.getRooms()
roomsRepository.getRooms(false)
.map { it.getOrNull().orEmpty() },
sessionFilters,
::AgendaLayoutState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ class AgendaPagerViewModel(
bookmarksRepository: BookmarksRepository,
private val applyForAppClinicUseCase: ApplyForAppClinicUseCase,
) : LceViewModel<List<DaySchedule>>(
produce = {
getAgendaUseCase()
produce = { refresh ->
getAgendaUseCase(refresh)
.combine(bookmarksRepository.favoriteSessions) { agendaResult, favoriteSessions ->
agendaResult.map { agenda -> agendaToDays(agenda, favoriteSessions) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update

abstract class LceViewModel<T>(
produce: () -> Flow<Result<T>>
produce: (refresh: Boolean) -> Flow<Result<T>>
) : ViewModel() {
private val loadingTrigger = MutableStateFlow(0)
private class LoadingTrigger(val refresh: Boolean)

private val loadingTrigger = MutableStateFlow(LoadingTrigger(refresh = false))

@OptIn(ExperimentalCoroutinesApi::class)
val values: StateFlow<Lce<T>> = loadingTrigger.flatMapLatest {
produce()
val values: StateFlow<Lce<T>> = loadingTrigger.flatMapLatest { trigger ->
produce(trigger.refresh)
.map { it.toLce() }
.onEach {
_isRefreshing.value = false
Expand All @@ -39,6 +40,6 @@ abstract class LceViewModel<T>(

fun refresh() {
_isRefreshing.value = true
loadingTrigger.update { it + 1 }
loadingTrigger.value = LoadingTrigger(refresh = true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ class SpeakerListViewModel(
speakersRepository: SpeakersRepository
) : ViewModel() {

val uiState: Flow<Lce<SpeakersUiState>> = speakersRepository.getSpeakers().map { result ->
result.map {
SpeakersUiState(
speakers = it
)
}.toLce()
}
val uiState: Flow<Lce<SpeakersUiState>> = speakersRepository.getSpeakers(false)
.map { result ->
result.map {
SpeakersUiState(
speakers = it
)
}.toLce()
}
}

data class SpeakersUiState(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import kotlinx.datetime.DateTimeUnit
Expand Down Expand Up @@ -62,21 +61,23 @@ class MainViewModel(
initialValue = userRepository.currentUser
)

private val bookmarksRefreshTrigger = MutableStateFlow(0)
private val sessionsRefreshTrigger = MutableStateFlow(0)
private class LoadingTrigger(val refresh: Boolean)

private val bookmarksSyncTrigger = MutableStateFlow(LoadingTrigger(refresh = true))
private val sessionsLoadingTrigger = MutableStateFlow(LoadingTrigger(refresh = false))

init {
viewModelScope.launch {
// Sync bookmarks when the user changes or a refresh is requested
combine(user, bookmarksRefreshTrigger) { currentUser, trigger -> currentUser }
combine(user, bookmarksSyncTrigger) { currentUser, _ -> currentUser }
.collectLatest { currentUser -> maybeSyncBookmarks(currentUser) }
}

viewModelScope.launch {
messageClient.getEventsFlow().collect { messageEvent ->
if (messageEvent.path == MESSAGE_SYNC_BOOKMARKS) {
Log.d(TAG, "Received syncBookmarks message")
bookmarksRefreshTrigger.update { it + 1 }
bookmarksSyncTrigger.value = LoadingTrigger(refresh = true)
}
}
}
Expand All @@ -103,8 +104,8 @@ class MainViewModel(

@OptIn(ExperimentalCoroutinesApi::class)
private val sessions: Flow<List<UISession>?> = combine(
sessionsRefreshTrigger
.flatMapLatest { getAgendaUseCase() }
sessionsLoadingTrigger
.flatMapLatest { trigger -> getAgendaUseCase(trigger.refresh) }
.mapNotNull { it.getOrNull() },
bookmarksRepository.favoriteSessions,
localPreferencesRepository.showOnlyBookmarkedSessions
Expand Down Expand Up @@ -149,8 +150,8 @@ class MainViewModel(
}

fun refresh() {
bookmarksRefreshTrigger.update { it + 1 }
sessionsRefreshTrigger.update { it + 1 }
bookmarksSyncTrigger.value = LoadingTrigger(refresh = true)
sessionsLoadingTrigger.value = LoadingTrigger(refresh = true)
}

companion object {
Expand Down

0 comments on commit fe13c55

Please sign in to comment.