Skip to content

Commit

Permalink
Paginate sessions
Browse files Browse the repository at this point in the history
  • Loading branch information
BoD committed May 6, 2024
1 parent 53fe3a9 commit 954e556
Show file tree
Hide file tree
Showing 14 changed files with 190 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class FakeAndroidMakersStore : RoomsRepository, VenueRepository, SpeakersReposit
TODO("Not yet implemented")
}

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

Expand Down
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ androidx-credentials-playServicesAuth = { group = "androidx.credentials", name =
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidxLifecycle" }
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" }
apollo-adapters = { module = "com.apollographql.apollo3:apollo-adapters", version.ref = "apollo" }
apollo-normalized-cache = { module = "com.apollographql.apollo3:apollo-normalized-cache", version.ref = "apollo" }
apollo-normalized-cache-sqlite = { module = "com.apollographql.apollo3:apollo-normalized-cache-sqlite", version.ref = "apollo" }
apollo-normalized-cache = { module = "com.apollographql.apollo3:apollo-normalized-cache-incubating", version.ref = "apollo" }
apollo-normalized-cache-sqlite = { module = "com.apollographql.apollo3:apollo-normalized-cache-sqlite-incubating", version.ref = "apollo" }
apollo-runtime = { module = "com.apollographql.apollo3:apollo-runtime", version.ref = "apollo" }
atomicfu = "org.jetbrains.kotlinx:atomicfu:0.23.2"
qdsfdhvh-imageloader = { module = "io.github.qdsfdhvh:image-loader", version.ref = "image-loader" }
Expand Down
14 changes: 12 additions & 2 deletions shared/data/src/commonMain/graphql/extra.graphqls
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
# noinspection GraphQLUnresolvedReference,GraphQLMissingType
extend schema
@link(
url: "https://specs.apollo.dev/kotlin_labs/v0.3",
import: ["@typePolicy", "@fieldPolicy"]
)

extend type Room @typePolicy(keyFields: "id")

extend type Session @typePolicy(keyFields: "id")
extend type RootQuery @fieldPolicy(forField: "session", keyArgs: "id")

extend type RootQuery
@fieldPolicy(forField: "session", keyArgs: "id")
@fieldPolicy(forField: "sessions", paginationArgs: "first after")
@typePolicy(embeddedFields: "sessions")

extend type Speaker @typePolicy(keyFields: "id")

extend type Venue @typePolicy(keyFields: "name")

extend type SessionConnection @typePolicy(embeddedFields: "pageInfo")
7 changes: 5 additions & 2 deletions shared/data/src/commonMain/graphql/operations.graphql
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
query GetSessions {
sessions(first: 1000) {
query GetSessions($after: String) {
sessions(first: 10, after: $after) {
nodes {
...SessionDetails
}
pageInfo {
endCursor
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package fr.androidmakers.store.graphql

import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.annotations.ApolloExperimental
import com.apollographql.apollo3.api.http.HttpRequest
import com.apollographql.apollo3.api.http.HttpResponse
import com.apollographql.apollo3.cache.normalized.api.FieldRecordMerger
import com.apollographql.apollo3.cache.normalized.api.FieldRecordMerger.FieldMerger
import com.apollographql.apollo3.cache.normalized.api.MemoryCacheFactory
import com.apollographql.apollo3.cache.normalized.api.MetadataGenerator
import com.apollographql.apollo3.cache.normalized.api.MetadataGeneratorContext
import com.apollographql.apollo3.cache.normalized.normalizedCache
import com.apollographql.apollo3.cache.normalized.sql.SqlNormalizedCacheFactory
import com.apollographql.apollo3.network.http.HttpInterceptor
Expand All @@ -17,25 +22,83 @@ fun ApolloClient(
userRepository: UserRepository,
): ApolloClient {
val memoryCacheFactory = MemoryCacheFactory(20_000_000).chain(sqlNormalizedCacheFactory)
@OptIn(ApolloExperimental::class)
return ApolloClient.Builder()
.serverUrl("https://androidmakers.fr/graphql")
.addHttpInterceptor(object : HttpInterceptor {
override suspend fun intercept(request: HttpRequest, chain: HttpInterceptorChain): HttpResponse {
return chain.proceed(
request.newBuilder()
.apply {
/**
*
*/
val token = getIdToken(userRepository)
if (token != null) {
addHeader("Authorization", "Bearer $token")
}
.serverUrl("https://androidmakers.fr/graphql")
.addHttpInterceptor(object : HttpInterceptor {
override suspend fun intercept(
request: HttpRequest,
chain: HttpInterceptorChain
): HttpResponse {
return chain.proceed(
request.newBuilder()
.apply {
/**
*
*/
val token = getIdToken(userRepository)
if (token != null) {
addHeader("Authorization", "Bearer $token")
}
.build()
)
}
})
.normalizedCache(memoryCacheFactory)
.build()
}
.build()
)
}
})
.normalizedCache(
normalizedCacheFactory = memoryCacheFactory,
metadataGenerator = AndroidMakersMetaDataGenerator,
recordMerger = FieldRecordMerger(AndroidMakersFieldMerger),
)
.build()
}

@OptIn(ApolloExperimental::class)
object AndroidMakersMetaDataGenerator : MetadataGenerator {
@Suppress("UNCHECKED_CAST")
override fun metadataForObject(obj: Any?, context: MetadataGeneratorContext): Map<String, Any?> {
return if (context.field.type.rawType().name == "SessionConnection") {
obj as Map<String, Any?>
val pageInfo = obj["pageInfo"] as? Map<String, Any?>
val endCursor = pageInfo?.get("endCursor") as? String
mapOf(
"endCursor" to endCursor,
"after" to context.argumentValue("after"),
)
} else {
emptyMap()
}
}
}

@OptIn(ApolloExperimental::class)
object AndroidMakersFieldMerger : FieldMerger {
@Suppress("UNCHECKED_CAST")
override fun mergeFields(
existing: FieldRecordMerger.FieldInfo,
incoming: FieldRecordMerger.FieldInfo
): FieldRecordMerger.FieldInfo {
val existingEndCursor = existing.metadata["endCursor"] as? String
val incomingAfterArgument = incoming.metadata["after"] as? String
return if (existingEndCursor == null && incomingAfterArgument == null) {
incoming
} else if (incomingAfterArgument == existingEndCursor) {
val existingValue = existing.value as Map<String, Any?>
val existingNodes = existingValue["nodes"] as? List<*>

val incomingValue = incoming.value as Map<String, Any?>
val incomingNodes = incomingValue["nodes"] as? List<*>

val mergedNodes: List<*> = existingNodes.orEmpty() + incomingNodes.orEmpty()
val mergedValue = (existingValue + incomingValue).toMutableMap()
mergedValue["nodes"] = mergedNodes

FieldRecordMerger.FieldInfo(
value = mergedValue,
metadata = incoming.metadata,
)
} else {
incoming
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package fr.androidmakers.store.graphql

import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.Optional
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.Session
import fr.androidmakers.domain.repo.SessionsRepository
import kotlinx.coroutines.flow.Flow
Expand Down Expand Up @@ -37,9 +39,22 @@ class SessionsGraphQLRepository(private val apolloClient: ApolloClient) : Sessio
.toResultFlow()
}

override fun getSessions(): Flow<Result<List<Session>>> {
return apolloClient.query(GetSessionsQuery())
.cacheAndNetwork()
.map { it.map { it.sessions.nodes.map { it.sessionDetails.toSession() } } }
override fun watchSessions(): Flow<List<Session>> {
return apolloClient.query(GetSessionsQuery(after = Optional.present(null)))
.fetchPolicy(FetchPolicy.CacheAndNetwork)
.watch()
.map { it.data?.sessions?.nodes?.map { it.sessionDetails.toSession() }.orEmpty() }
}

override suspend fun fetchNextSessionsPage() {
val cacheResponse = apolloClient.query(GetSessionsQuery())
.fetchPolicy(FetchPolicy.CacheOnly)
.execute()
val endCursor = cacheResponse.data?.sessions?.pageInfo?.endCursor
if (endCursor != null) {
apolloClient.query(GetSessionsQuery(Optional.present(endCursor)))
.fetchPolicy(FetchPolicy.NetworkOnly)
.execute()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package fr.androidmakers.di

import fr.androidmakers.domain.interactor.ApplyForAppClinicUseCase
import fr.androidmakers.domain.interactor.FetchNextSessionsPageUseCase
import fr.androidmakers.domain.interactor.GetAfterpartyVenueUseCase
import fr.androidmakers.domain.interactor.GetAgendaUseCase
import fr.androidmakers.domain.interactor.GetConferenceVenueUseCase
Expand All @@ -22,6 +23,7 @@ expect val domainPlatformModule: Module

val domainModule = module {
factory { GetAgendaUseCase(get(), get(), get()) }
factory { FetchNextSessionsPageUseCase(get()) }
factory { GetConferenceVenueUseCase(get()) }
factory { GetAfterpartyVenueUseCase(get()) }
factory { MergeBookmarksUseCase(get(), get()) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package fr.androidmakers.domain.interactor

import fr.androidmakers.domain.repo.SessionsRepository

class FetchNextSessionsPageUseCase(
private val sessionsRepository: SessionsRepository,
) {
suspend operator fun invoke() {
sessionsRepository.fetchNextSessionsPage()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,11 @@ class GetAgendaUseCase(
) {
operator fun invoke(): Flow<Result<Agenda>> {
return combine(
sessionsRepository.getSessions(),
sessionsRepository.watchSessions(),
roomsRepository.getRooms(),
speakersRepository.getSpeakers(),
) { sessions, rooms, speakers ->

sessions.exceptionOrNull()?.let {
it.printStackTrace()
return@combine Result.failure(it)
}
rooms.exceptionOrNull()?.let {
it.printStackTrace()
return@combine Result.failure(it)
Expand All @@ -34,7 +30,7 @@ class GetAgendaUseCase(

Result.success(
Agenda(
sessions = sessions.getOrThrow().associateBy { it.id },
sessions = sessions.associateBy { it.id },
rooms = rooms.getOrThrow().associateBy { it.id },
speakers = speakers.getOrThrow().associateBy { it.id }
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import kotlinx.coroutines.flow.Flow
interface SessionsRepository {
fun getSession(id: String): Flow<Result<Session>>

fun getSessions(): Flow<Result<List<Session>>>
fun watchSessions(): Flow<List<Session>>

suspend fun fetchNextSessionsPage()

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ val viewModelModule = module {
factory { VenueViewModel(get(), get(), get()) }
factory { (speakerId: String) -> SpeakerDetailsViewModel(speakerId, get(), get()) }
factory { AgendaLayoutViewModel(get()) }
factory { AgendaPagerViewModel(get(), get(), get(), get()) }
factory { AgendaPagerViewModel(get(), get(), get(), get(), get()) }
factory { (sessionId: String) -> SessionDetailViewModel(sessionId, get(), get(), get(), get(), get(), get(), get(), get()) }
factory { AboutViewModel(get(), get(), get(), get(), get()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ fun AgendaColumn(
onSessionClicked: (UISession) -> Unit,
onSessionBookmarked: (UISession, Boolean) -> Unit,
onApplyForAppClinicClicked: () -> Unit,
onFetchMoreItems: () -> Unit,
) {
val listState = rememberLazyListState()

Expand Down Expand Up @@ -104,6 +105,10 @@ fun AgendaColumn(
}
}
}

item {
onFetchMoreItems()
}
}
}

Expand Down
Loading

0 comments on commit 954e556

Please sign in to comment.