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

Update apollo to 4.0.0 #92

Merged
merged 3 commits into from
Sep 23, 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
10 changes: 5 additions & 5 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ ktlintGradlePlugin = "12.1.1"
ktlint = "1.2.1"
detektGradlePlugin = "1.23.6"
composeLint = "1.3.1"
apollo = "3.8.4"
apollo = "4.0.0"
ktorfit = "2.1.0" # Must be compatible with: `ksp`
ktorfitConverter = "2.1.0"
ktor = "2.3.12" # Must be compatible with: `ktorfit`
Expand Down Expand Up @@ -116,9 +116,9 @@ skie-annotations = { group = "co.touchlab.skie", name = "configuration-annotatio
moko-resources = { group = "dev.icerock.moko", name = "resources", version.ref = "moko-resources" }

# Network
network-apollo-runtime = { group = "com.apollographql.apollo3", name = "apollo-runtime", version.ref = "apollo" }
network-apollo-normalizedCache-core = { group = "com.apollographql.apollo3", name = "apollo-normalized-cache", version.ref = "apollo" }
network-apollo-normalizedCache-sqlite = { group = "com.apollographql.apollo3", name = "apollo-normalized-cache-sqlite", version.ref = "apollo" }
network-apollo-runtime = { group = "com.apollographql.apollo", name = "apollo-runtime", version.ref = "apollo" }
network-apollo-normalizedCache-core = { group = "com.apollographql.apollo", name = "apollo-normalized-cache", version.ref = "apollo" }
network-apollo-normalizedCache-sqlite = { group = "com.apollographql.apollo", name = "apollo-normalized-cache-sqlite", version.ref = "apollo" }
network-ktorfit-ksp = { group = "de.jensklingenberg.ktorfit", name = "ktorfit-ksp", version.ref = "ktorfit" }
network-ktorfit-lib = { group = "de.jensklingenberg.ktorfit", name = "ktorfit-lib", version.ref = "ktorfit" }
network-ktorfit-converters = { group = "de.jensklingenberg.ktorfit", name = "ktorfit-converters-response", version.ref = "ktorfitConverter" }
Expand Down Expand Up @@ -148,7 +148,7 @@ kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
apollo = { id = "com.apollographql.apollo3", version.ref = "apollo" }
apollo = { id = "com.apollographql.apollo", version.ref = "apollo" }
ktorfit = { id = "de.jensklingenberg.ktorfit", version.ref = "ktorfit" }
skie = { id = "co.touchlab.skie", version.ref = "skie" }
buildkonfig = { id = "com.codingfeline.buildkonfig", version.ref = "buildkonfig" }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package app.futured.kmptemplate.network.graphql.cache

import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.cache.normalized.apolloStore
import com.apollographql.apollo.ApolloClient
import com.apollographql.apollo.cache.normalized.apolloStore
import org.koin.core.annotation.Single

@Single
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package app.futured.kmptemplate.network.graphql.cache

import com.apollographql.apollo3.cache.normalized.api.MemoryCacheFactory
import com.apollographql.apollo3.cache.normalized.api.NormalizedCacheFactory
import com.apollographql.apollo3.cache.normalized.sql.SqlNormalizedCacheFactory
import com.apollographql.apollo.cache.normalized.api.MemoryCacheFactory
import com.apollographql.apollo.cache.normalized.api.NormalizedCacheFactory
import com.apollographql.apollo.cache.normalized.sql.SqlNormalizedCacheFactory
import org.koin.core.annotation.Single

@Single
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package app.futured.kmptemplate.network.graphql.cache

import app.futured.kmptemplate.network.graphql.cache.NormalizedCacheKeyGenerator.Companion.KNOWN_TYPES
import co.touchlab.kermit.Logger
import com.apollographql.apollo3.cache.normalized.api.CacheKey
import com.apollographql.apollo3.cache.normalized.api.CacheKeyGenerator
import com.apollographql.apollo3.cache.normalized.api.CacheKeyGeneratorContext
import com.apollographql.apollo3.cache.normalized.api.Record
import com.apollographql.apollo.cache.normalized.api.CacheKey
import com.apollographql.apollo.cache.normalized.api.CacheKeyGenerator
import com.apollographql.apollo.cache.normalized.api.CacheKeyGeneratorContext
import com.apollographql.apollo.cache.normalized.api.Record
import org.koin.core.annotation.Single

private typealias Typename = String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import app.futured.kmptemplate.network.graphql.ext.isUnauthorizedCloudError
import app.futured.kmptemplate.network.graphql.result.CloudErrorCode
import app.futured.kmptemplate.network.graphql.result.NetworkError
import app.futured.kmptemplate.network.graphql.result.NetworkResult
import com.apollographql.apollo3.api.Mutation
import com.apollographql.apollo3.api.Query
import com.apollographql.apollo.api.Mutation
import com.apollographql.apollo.api.Query
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
Expand All @@ -25,41 +25,44 @@ internal interface ApiManager {
*
* @param query to be executed
* @param fetchPolicy fetch policy to apply to this query
* @param throwOnPartialData whether to throw an exception on partial data
* @return the result of the [query] wrapped with [NetworkResult]
*/
suspend fun <DATA : Query.Data> executeQuery(
query: Query<DATA>,
fetchPolicy: FetchPolicy = FetchPolicy.NetworkOnly,
throwOnPartialData: Boolean = false,
): NetworkResult<DATA> =
runWrapping { errorInterceptor { apiAdapter.executeQuery(query, fetchPolicy) } }
runWrapping { errorInterceptor { apiAdapter.executeQuery(query, fetchPolicy, throwOnPartialData) } }

/**
* Executes the [Query] and then watches it from normalized cache.
* Returns Flow of [DATA] wrapped with [NetworkResult].
*
* @param query to be executed and watched.
* @param fetchPolicy fetch policy to apply to initial fetch.
* @param fetchThrows whether to emit [NetworkResult.Failure]s during the initial fetch.
* @param refetchThrows whether to emit [NetworkResult.Failure]s during a refetch.
* @param filterOutExceptions whether to filter out exceptions from the flow.
* @param throwOnPartialData whether to throw an exception on partial data
* @return Flow of [DATA] wrapped in [NetworkResult]
*/
fun <DATA : Query.Data> executeQueryWatcher(
query: Query<DATA>,
fetchPolicy: FetchPolicy,
fetchThrows: Boolean = true,
refetchThrows: Boolean = false,
filterOutExceptions: Boolean = false,
throwOnPartialData: Boolean = false,
): Flow<NetworkResult<DATA>> = apiAdapter
.watchQueryWatcher(query, fetchPolicy, fetchThrows, refetchThrows)
.watchQueryWatcher(query, fetchPolicy, filterOutExceptions, throwOnPartialData)
.map { result -> runWrapping { errorInterceptor { result.getOrThrow() } } }

/**
* Executes the [Mutation] and returns the [DATA] response wrapped with [NetworkResult].
*
* @param mutation to be executed
* @param throwOnPartialData whether to throw an exception on partial data
* @return the result of the [mutation] wrapped with [NetworkResult]
*/
suspend fun <DATA : Mutation.Data> executeMutation(mutation: Mutation<DATA>): NetworkResult<DATA> =
runWrapping { errorInterceptor { apiAdapter.executeMutation(mutation) } }
suspend fun <DATA : Mutation.Data> executeMutation(mutation: Mutation<DATA>, throwOnPartialData: Boolean = false): NetworkResult<DATA> =
runWrapping { errorInterceptor { apiAdapter.executeMutation(mutation, throwOnPartialData) } }

/**
* Executes [operation], wrapping result in [NetworkResult].
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package app.futured.kmptemplate.network.graphql.client

import app.futured.kmptemplate.network.graphql.result.NetworkError
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.ApolloResponse
import com.apollographql.apollo3.api.Mutation
import com.apollographql.apollo3.api.Operation
import com.apollographql.apollo3.api.Query
import com.apollographql.apollo3.cache.normalized.fetchPolicy
import com.apollographql.apollo3.cache.normalized.watch
import com.apollographql.apollo3.exception.ApolloException
import com.apollographql.apollo3.exception.ApolloHttpException
import com.apollographql.apollo3.exception.ApolloNetworkException
import com.apollographql.apollo.ApolloClient
import com.apollographql.apollo.api.ApolloResponse
import com.apollographql.apollo.api.Mutation
import com.apollographql.apollo.api.Operation
import com.apollographql.apollo.api.Query
import com.apollographql.apollo.cache.normalized.fetchPolicy
import com.apollographql.apollo.cache.normalized.watch
import com.apollographql.apollo.exception.ApolloHttpException
import com.apollographql.apollo.exception.ApolloNetworkException
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import org.koin.core.annotation.Single

Expand All @@ -29,47 +29,51 @@ internal class ApolloApiAdapter(
*
* @param query to be executed
* @param fetchPolicy fetch policy to apply
* @param throwOnPartialData whether to throw an exception on partial data
* @return [DATA] as a result of the [query]
*/
suspend fun <DATA : Query.Data> executeQuery(query: Query<DATA>, fetchPolicy: FetchPolicy): DATA =
executeOperation { apolloClient.query(query).fetchPolicy(fetchPolicy.asApolloFetchPolicy()).execute() }
suspend fun <DATA : Query.Data> executeQuery(query: Query<DATA>, fetchPolicy: FetchPolicy, throwOnPartialData: Boolean): DATA =
executeOperation(throwOnPartialData) { apolloClient.query(query).fetchPolicy(fetchPolicy.asApolloFetchPolicy()).execute() }

/**
* Executes the [Query] and then watches it from normalized cache.
*
* @param query to be executed and watched.
* @param filterOutExceptions whether to filter out exceptions from the flow.
* @param throwOnPartialData whether to throw an exception on partial data
* @param fetchPolicy fetch policy to apply to initial fetch.
* @param fetchThrows whether to throw if an [ApolloException] happens during the initial fetch.
* @param refetchThrows whether to throw if an [ApolloException] happens during a refetch.
* @return Flow of [DATA] wrapped in Kotlin result
*/
fun <DATA : Query.Data> watchQueryWatcher(
query: Query<DATA>,
fetchPolicy: FetchPolicy,
fetchThrows: Boolean = true,
refetchThrows: Boolean = false,
filterOutExceptions: Boolean,
throwOnPartialData: Boolean,
): Flow<Result<DATA>> =
apolloClient
.query(query)
.fetchPolicy(fetchPolicy.asApolloFetchPolicy())
.watch(fetchThrows = fetchThrows, refetchThrows = refetchThrows)
.map { apolloResponse -> runCatching { executeOperation { apolloResponse } } }
.watch()
.filter { if (filterOutExceptions) it.exception == null else true }
.map { apolloResponse -> runCatching { executeOperation(throwOnPartialData) { apolloResponse } } }


/**
* Executes the [Mutation] and returns the [DATA].
*
* @param mutation to be executed
* @param throwOnPartialData whether to throw an exception on partial data
* @return [DATA] as a result of the [mutation]
*/
suspend fun <DATA : Mutation.Data> executeMutation(mutation: Mutation<DATA>): DATA =
executeOperation { apolloClient.mutation(mutation).execute() }
suspend fun <DATA : Mutation.Data> executeMutation(mutation: Mutation<DATA>, throwOnPartialData: Boolean): DATA =
executeOperation(throwOnPartialData) { apolloClient.mutation(mutation).execute() }

private suspend fun <DATA : Operation.Data> executeOperation(
throwOnPartialData: Boolean,
apolloCall: suspend () -> ApolloResponse<DATA>,
): DATA {
val result = runCatching {
unfoldResult(apolloCall())
unfoldResult(apolloCall(), throwOnPartialData)
}

result.fold(
Expand All @@ -95,16 +99,41 @@ internal class ApolloApiAdapter(
}

/**
* Unwraps successful [ApolloResponse] either into [DATA], or throws [NetworkError.CloudError]
* in case errors are present in the response.
* https://www.apollographql.com/docs/kotlin/essentials/errors#truth-table
* Unwraps successful [ApolloResponse] either into [DATA], or throws [NetworkError].
* If the response contains an exception, it throws the exception.
* If the response contains complete data, it returns the data.
* If the response contains partial data, it returns the data or throws an exception (It's possible to have both
* [ApolloResponse.data] non-null and [ApolloResponse.errors] non-null.)
* If the response contains errors, it throws [NetworkError.CloudError].
* @param throwOnPartialData whether to throw an exception on partial data
*/
private fun <DATA : Operation.Data> unfoldResult(response: ApolloResponse<DATA>): DATA =
if (response.hasErrors().not()) {
response.data ?: error("Response without errors and with no data")
} else {
throw NetworkError.CloudError(
code = errorResponseParser.getErrorResponseCode(response),
message = errorResponseParser.getErrorMessage(response) ?: "",
)
private fun <DATA : Operation.Data> unfoldResult(
response: ApolloResponse<DATA>,
throwOnPartialData: Boolean,
): DATA {
response.exception?.let { throw it }

response.data?.let { data ->
if (response.hasErrors()) {
if (throwOnPartialData) {
throw NetworkError.CloudError(
code = errorResponseParser.getErrorResponseCode(response),
message = errorResponseParser.getErrorMessage(response),
)
}
// Return partial data
return data
}
// Return complete data
return data
}

// Non-compliant server, if data is null, there should be at least one error, or
// A GraphQL request error happened or a Graph field error bubbled up.
throw NetworkError.CloudError(
code = errorResponseParser.getErrorResponseCode(response),
message = errorResponseParser.getErrorMessage(response)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import app.futured.kmptemplate.network.graphql.cache.NetworkNormalizedCacheFacto
import app.futured.kmptemplate.network.graphql.cache.NormalizedCacheKeyGenerator
import app.futured.kmptemplate.network.graphql.interceptor.LoggingInterceptorFactory
import app.futured.kmptemplate.network.graphql.tools.Constants
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.normalizedCache
import com.apollographql.apollo3.network.http.DefaultHttpEngine
import com.apollographql.apollo3.network.http.HttpNetworkTransport
import com.apollographql.apollo.ApolloClient
import com.apollographql.apollo.cache.normalized.FetchPolicy
import com.apollographql.apollo.cache.normalized.fetchPolicy
import com.apollographql.apollo.cache.normalized.normalizedCache
import com.apollographql.apollo.network.http.DefaultHttpEngine
import com.apollographql.apollo.network.http.HttpNetworkTransport
import org.koin.core.annotation.Single

@Single
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package app.futured.kmptemplate.network.graphql.client

import app.futured.kmptemplate.network.graphql.result.CloudErrorCode
import com.apollographql.apollo3.api.ApolloResponse
import com.apollographql.apollo.api.ApolloResponse
import org.koin.core.annotation.Single

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package app.futured.kmptemplate.network.graphql.client

import com.apollographql.apollo3.exception.ApolloCompositeException
import com.apollographql.apollo3.exception.ApolloException
import com.apollographql.apollo3.exception.CacheMissException
import com.apollographql.apollo3.cache.normalized.FetchPolicy as ApolloFetchPolicy
import com.apollographql.apollo.api.ApolloResponse
import com.apollographql.apollo.exception.ApolloException
import com.apollographql.apollo.cache.normalized.FetchPolicy as ApolloFetchPolicy

enum class FetchPolicy {
/**
* Try the cache, if that failed, try the network.
*
* An [ApolloCompositeException] is thrown if the data is not in the cache and the network call failed.
* If coming from the cache 1 value is emitted, otherwise 1 or multiple values can be emitted from the network.
* This [FetchPolicy] emits one or more [ApolloResponse].
* Cache misses and network errors have [ApolloResponse.exception] set to a non-null [ApolloException].
*
* This is the default behaviour.
*/
Expand All @@ -19,31 +18,33 @@ enum class FetchPolicy {
/**
* Only try the cache.
*
* A [CacheMissException] is thrown if the data is not in the cache, otherwise 1 value is emitted.
* This [FetchPolicy] emits one [ApolloResponse].
* Cache misses have [ApolloResponse.exception] set to a non-null [ApolloException].
*/
CacheOnly,

/**
* Try the network, if that failed, try the cache.
*
* An [ApolloCompositeException] is thrown if the network call failed and the data is not in the cache.
* If coming from the network 1 or multiple values can be emitted, otherwise 1 value is emitted from the cache.
* This [FetchPolicy] emits one or more [ApolloResponse].
* Cache misses and network errors have [ApolloResponse.exception] set to a non-null [ApolloException].
*/
NetworkFirst,

/**
* Only try the network.
*
* An [ApolloException] is thrown if the network call failed, otherwise 1 or multiple values can be emitted.
* This FetchPolicy emits one or more [ApolloResponse].
* Several [ApolloResponse] may be emitted if your NetworkTransport supports it, for example with @defer.
* Network errors have [ApolloResponse.exception] set to a non-null[ApolloException].
*/
NetworkOnly,

/**
* Try the cache, then also try the network.
*
* If the data is in the cache, it is emitted, if not, no exception is thrown at that point. Then the network call is made, and if
* successful the value(s) are emitted, otherwise either an [ApolloCompositeException] (both cache miss and network failed) or an
* [ApolloException] (only network failed) is thrown.
* This FetchPolicy emits two or more [ApolloResponse].
* Cache misses and network errors have [ApolloResponse.exception] set to a non-null [ApolloException].
*/
CacheAndNetwork,
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package app.futured.kmptemplate.network.graphql.injection

import app.futured.kmptemplate.network.graphql.client.ApolloClientFactory
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo.ApolloClient
import org.koin.core.annotation.ComponentScan
import org.koin.core.annotation.Module
import org.koin.core.annotation.Single
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package app.futured.kmptemplate.network.graphql.interceptor

import co.touchlab.kermit.Logger
import com.apollographql.apollo3.network.http.LoggingInterceptor
import com.apollographql.apollo.network.http.LoggingInterceptor
import org.koin.core.annotation.Single

@Single
Expand Down
Loading