Skip to content

Commit

Permalink
Make the app work offline (#287)
Browse files Browse the repository at this point in the history
* make the app work offline

* remove watch

* merge bookmarks on signin
  • Loading branch information
martinbonnin authored Apr 19, 2024
1 parent 9e36600 commit 3a8bb6a
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 134 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,14 @@ class MainActivity : AppCompatActivity() {
val result = auth.signInWithCredential(firebaseCredential)
// Sign in success, update UI with the signed-in user's information
lifecycleScope.launch {
UserData().userRepository.setUser(result.user)
UserData().apply {
userRepository.setUser(result.user)
val uid = result.user?.uid
if (uid != null) {
syncBookmarksUseCase(uid)
}
}

println("user id=${result.user?.uid}")
println("idToken=${result.user?.getIdToken(true)}")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,41 @@
package fr.androidmakers.store.graphql

import com.apollographql.apollo3.ApolloCall
import com.apollographql.apollo3.api.ApolloResponse
import com.apollographql.apollo3.api.Operation
import com.apollographql.apollo3.cache.normalized.FetchPolicy
import com.apollographql.apollo3.cache.normalized.fetchPolicy
import com.apollographql.apollo3.exception.ApolloException
import com.apollographql.apollo3.exception.CacheMissException
import com.apollographql.apollo3.exception.DefaultApolloException
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flow

internal fun <T : Operation.Data> Flow<ApolloResponse<T>>.ignoreCacheMisses(): Flow<ApolloResponse<T>> {
return filterNot {
// Ignore cache misses
it.exception is CacheMissException
}
}


internal fun <T : Operation.Data> ApolloCall<T>.cacheAndNetwork(): Flow<Result<T>> {
return flow {
var hasData = false
var exception: ApolloException? = null
fetchPolicy(FetchPolicy.CacheAndNetwork)
.toFlow()
.collect {
if (it.data != null) {
hasData = true
emit(Result.success(it.data!!))
} else {
exception = it.exception ?: DefaultApolloException("No data found")
}
}
if (!hasData) {
emit(Result.failure(exception!!))
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package fr.androidmakers.store.graphql

import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.cache.normalized.FetchPolicy
import com.apollographql.apollo3.cache.normalized.fetchPolicy
import com.apollographql.apollo3.cache.normalized.watch
import fr.androidmakers.domain.model.Partner
import fr.androidmakers.domain.model.PartnerGroup
import fr.androidmakers.domain.repo.PartnersRepository
Expand All @@ -13,24 +10,23 @@ import kotlinx.coroutines.flow.map
class PartnersGraphQLRepository(private val apolloClient: ApolloClient): PartnersRepository {
override fun getPartners(): Flow<Result<List<PartnerGroup>>> {
return apolloClient.query(GetPartnerGroupsQuery())
.fetchPolicy(FetchPolicy.CacheAndNetwork)
.watch()
.ignoreCacheMisses()
.cacheAndNetwork()
.map {
it.dataAssertNoErrors.partnerGroups.map { partnerGroup ->
PartnerGroup(
it.map {
it.partnerGroups.map { partnerGroup ->
PartnerGroup(
title = partnerGroup.title,
partners = partnerGroup.partners.map { partner ->
Partner(
logoUrl = partner.logoUrlLight,
name = partner.name,
url = partner.url,
logoUrlDark = partner.logoUrlDark
logoUrl = partner.logoUrlLight,
name = partner.name,
url = partner.url,
logoUrlDark = partner.logoUrlDark
)
}
)
)
}
}
}
.toResultFlow()
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package fr.androidmakers.store.graphql

import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.cache.normalized.FetchPolicy
import com.apollographql.apollo3.cache.normalized.fetchPolicy
import com.apollographql.apollo3.cache.normalized.watch
import fr.androidmakers.domain.model.Room
import fr.androidmakers.domain.repo.RoomsRepository
import kotlinx.coroutines.flow.Flow
Expand All @@ -18,11 +15,7 @@ class RoomsGraphQLRepository(private val apolloClient: ApolloClient): RoomsRepos

override fun getRooms(): Flow<Result<List<Room>>> {
return apolloClient.query(GetRoomsQuery())
.fetchPolicy(FetchPolicy.NetworkFirst)
.watch()
.map {
it.dataAssertNoErrors.rooms.map { it.roomDetails.toRoom() }
}
.toResultFlow()
.cacheAndNetwork()
.map { it.map { it.rooms.map { it.roomDetails.toRoom() } } }
}
}
Original file line number Diff line number Diff line change
@@ -1,113 +1,45 @@
package fr.androidmakers.store.graphql

import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.Mutation
import com.apollographql.apollo3.cache.normalized.FetchPolicy
import com.apollographql.apollo3.cache.normalized.apolloStore
import com.apollographql.apollo3.cache.normalized.fetchPolicy
import com.apollographql.apollo3.cache.normalized.optimisticUpdates
import com.apollographql.apollo3.cache.normalized.refetchPolicy
import com.apollographql.apollo3.cache.normalized.watch
import fr.androidmakers.domain.model.Session
import fr.androidmakers.domain.repo.SessionsRepository
import fr.androidmakers.store.graphql.type.buildBookmarkConnection
import fr.androidmakers.store.graphql.type.buildSession
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

class SessionsGraphQLRepository(private val apolloClient: ApolloClient) : SessionsRepository {

suspend fun addBookmark(uid: String?, sessionId: String): Boolean {
return modifyBookmarks(uid, AddBookmarkMutation(sessionId)) { sessionIds ->
AddBookmarkMutation.Data {
addBookmark = buildBookmarkConnection {
nodes = (sessionIds + sessionId).map {
buildSession {
this.id = it
}
}
}
}
}
}

suspend fun removeBookmark(uid: String?, sessionId: String): Boolean {
return modifyBookmarks(uid, RemoveBookmarkMutation(sessionId)) { sessionIds ->
RemoveBookmarkMutation.Data {
removeBookmark = buildBookmarkConnection {
nodes = (sessionIds - sessionId).map {
buildSession {
this.id = it
}
}
}
}
}
}

override suspend fun setBookmark(userId: String, sessionId: String, value: Boolean) {
try {
if (value) {
addBookmark(userId, sessionId)
} else {
removeBookmark(userId, sessionId)
}
} catch (e: Exception) {
e.printStackTrace()
val mutation = if (value) {
AddBookmarkMutation(sessionId)
} else {
RemoveBookmarkMutation(sessionId)
}
val response = apolloClient.mutation(mutation).execute()
if (response.exception != null) {
response.exception!!.printStackTrace()
}
}

override fun getSession(id: String): Flow<Result<Session>> {
return apolloClient.query(GetSessionQuery(id))
.fetchPolicy(FetchPolicy.CacheAndNetwork)
.watch()
.ignoreCacheMisses()
.map {
it.dataAssertNoErrors.session.sessionDetails.toSession()
}
.toResultFlow()
.cacheAndNetwork()
.map { it.map { it.session.sessionDetails.toSession() } }
}

override fun getBookmarks(uid: String): Flow<Result<Set<String>>> {
return apolloClient.query(BookmarksQuery())
.fetchPolicy(FetchPolicy.NetworkOnly)
.refetchPolicy(FetchPolicy.CacheOnly)
.watch().map {
it.data!!.bookmarkConnection!!.nodes.map { it.id }.toSet()
}.toResultFlow()
}

private suspend fun <D : Mutation.Data> modifyBookmarks(
uid: String?,
mutation: Mutation<D>,
data: (sessionIds: List<String>) -> D
): Boolean {
val optimisticData = try {
val bookmarks = apolloClient.apolloStore.readOperation(BookmarksQuery()).bookmarkConnection
data(bookmarks!!.nodes.map { it.id })
} catch (e: Exception) {
null
}
val response = apolloClient.mutation(mutation)
.apply {
if (optimisticData != null) {
optimisticUpdates(optimisticData)
}
.toFlow()
.map {
it.dataAssertNoErrors.bookmarkConnection.nodes.map { it.id }.toSet()
}
.execute()

return response.data != null
.toResultFlow()
}

override fun getSessions(): Flow<Result<List<Session>>> {
return apolloClient.query(GetSessionsQuery())
.fetchPolicy(FetchPolicy.CacheAndNetwork)
.watch()
.ignoreCacheMisses()
.map {
it.dataAssertNoErrors.sessions.nodes.map { it.sessionDetails.toSession() }
}
.toResultFlow()
.cacheAndNetwork()
.map { it.map { it.sessions.nodes.map { it.sessionDetails.toSession() } } }
}

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package fr.androidmakers.store.graphql

import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.cache.normalized.FetchPolicy
import com.apollographql.apollo3.cache.normalized.fetchPolicy
import com.apollographql.apollo3.cache.normalized.watch
import com.apollographql.apollo3.exception.DefaultApolloException
import fr.androidmakers.domain.model.Speaker
import fr.androidmakers.domain.repo.SpeakersRepository
import kotlinx.coroutines.flow.Flow
Expand All @@ -13,24 +11,24 @@ class SpeakersGraphQLRepository(private val apolloClient: ApolloClient) : Speake

override fun getSpeakers(): Flow<Result<List<Speaker>>> {
return apolloClient.query(GetSpeakersQuery())
.fetchPolicy(FetchPolicy.CacheAndNetwork)
.watch()
.ignoreCacheMisses()
.map {
it.dataAssertNoErrors.speakers.map { it.speakerDetails.toSpeaker() }
}.toResultFlow()
.cacheAndNetwork()
.map { it.map { it.speakers.map { it.speakerDetails.toSpeaker() } } }
}

override fun getSpeaker(id: String): Flow<Result<Speaker>> {
return apolloClient.query(GetSpeakersQuery())
.fetchPolicy(FetchPolicy.CacheAndNetwork)
.watch()
.ignoreCacheMisses()
.cacheAndNetwork()
.map {
it.dataAssertNoErrors.speakers.map { it.speakerDetails }.singleOrNull { it.id == id }
?.toSpeaker()
?: error("no speaker")
if (it.isSuccess) {
val speaker = it.getOrThrow().speakers.map { it.speakerDetails }.singleOrNull { it.id == id }?.toSpeaker()
if (speaker != null) {
Result.success(speaker)
} else {
Result.failure(DefaultApolloException("Something wrong happend"))
}
} else {
Result.failure(it.exceptionOrNull()!!)
}
}
.toResultFlow()
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package fr.androidmakers.store.graphql

import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.cache.normalized.FetchPolicy
import com.apollographql.apollo3.cache.normalized.fetchPolicy
import com.apollographql.apollo3.cache.normalized.watch
import fr.androidmakers.domain.model.Venue
import fr.androidmakers.domain.repo.VenueRepository
import kotlinx.coroutines.flow.Flow
Expand All @@ -12,12 +9,7 @@ import kotlinx.coroutines.flow.map
class VenueGraphQLRepository(private val apolloClient: ApolloClient): VenueRepository {
override fun getVenue(id: String): Flow<Result<Venue>> {
return apolloClient.query(GetVenueQuery(id))
.fetchPolicy(FetchPolicy.CacheAndNetwork)
.watch()
.ignoreCacheMisses()
.map {
it.dataAssertNoErrors.venue.toVenue()
}
.toResultFlow()
.cacheAndNetwork()
.map { it.map { it.venue.toVenue() } }
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.androidmakers.ui.common.navigation

import fr.androidmakers.domain.interactor.SyncBookmarksUseCase
import fr.androidmakers.domain.repo.UserRepository
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

class UserData: KoinComponent {
val userRepository: UserRepository by inject()
val syncBookmarksUseCase: SyncBookmarksUseCase by inject()
}


0 comments on commit 3a8bb6a

Please sign in to comment.