releases.shpreview
Apollo GraphQL/Apollo Kotlin

Apollo Kotlin

$npx -y @buildinternet/releases show apollo-kotlin
Mon
Wed
Fri
AprMayJunJulAugSepOctNovDecJanFebMarApr
Less
More
Releases8Avg2/moVersionsv4.4.0 → v5.0.0-alpha.7
Feb 7, 2022

Version 3.1.0 introduces new APIs for testing, mapping scalars as well a redesigned cache pipeline. It also contains bugfixes around the @include directives, MemoryCache and GraphQL validation amongst other changes.

⚙️ [breaking] Fragment package name and useSchemaPackageNameForFragments (#3775)

If you're using packageNameFromFilePath(), the package name of generated fragment classes has changed.

Different generated types have different package names:

  • Generated types coming from operations are generated based on the operation path
  • Generated types coming from the schema (input objects, custom scalars and enums) are generated based on the schema path

Previously, fragments were using the schema path which is inconsistent because fragments are not defined in the schema but are executable files, like operations.

Version 3.1.0 now uses the same logic for fragments as for operations. To revert to the previous behaviour, you can use useSchemaPackageNameForFragments:

apollo {
  useSchemaPackageNameForFragments.set(true)
}

This is also done automatically if you're using useVersion2Compat(). Moving forward, the plan is to remove useSchemaPackageNameForFragments in favor of setting a custom PackageNameGenerator. If you have use cases that require useSchemaPackageNameForFragments, please reach out.

✨ [New] QueueTestNetworkTransport (#3757)

3.1.0 introduces QueueTestNetworkTransport to test at the GraphQL layer without needing to run an HTTP server.

To use it, configure your ApolloClient:

// This uses a QueueTestNetworkTransport that will play the queued responses
val apolloClient = ApolloClient.Builder()
    .networkTransport(QueueTestNetworkTransport())
    .build()

You can then use the enqueueTestResponse extension function to specify the GraphQL responses to return:

val testQuery = GetHeroQuery("001")
val testData = GetHeroQuery.Data {
  hero = droidHero {
    name = "R2D2"
  }
}
apolloClient.enqueueTestResponse(testQuery, testData)
val actual = apolloClient.query(testQuery).execute().data!!
assertEquals(testData.hero.name, actual.hero.name)

✨ [New] MockServerHandler (#3757)

If you're testing at the HTTP layer, you can now define your own MockServerHandler to customize how the server is going to answer to requests:

val customHandler = object : MockServerHandler {
  override fun handle(request: MockRequest): MockResponse {
    return if (/* Your custom logic here */) {
      MockResponse(
          body = """{"data": {"random": 42}}""",
          headers = mapOf("X-Test" to "true"),
      )
    } else {
      MockResponse(
          body = "Internal server error",
          statusCode = 500,
      )
    }
  }
}
val mockServer = MockServer(customHandler)

✨ [New] FetchPolicy.CacheAndNetwork (#3828)

Previously, FetchPolicys were limited to policies that emitted at most one response. There was a executeCacheAndNetwork() method but it felt asymmetrical. This version introduces FetchPolicy.CacheAndNetwork that can emit up to two responses:

apolloClient.query(query)
  // Check the cache and also use the network (1 or 2 values can be emitted)
  .fetchPolicy(FetchPolicy.CacheAndNetwork)
  // Execute the query and collect the responses
  .toFlow().collect { response ->
      // ...
  }

✨ [New] ApolloCall<D>.fetchPolicyInterceptor(interceptor: ApolloInterceptor) (#3743)

If you need more customized ways to fetch data from the cache or more fine-grained error handling that does not come with the built-in FetchPolicy, you can now use fetchPolicyInterceptor:

// An, interceptor that will only use the network after getting a successful response
val refetchPolicyInterceptor = object : ApolloInterceptor {
  var hasSeenValidResponse: Boolean = false
  override fun <D : Operation.Data> intercept(request: ApolloRequest<D>, chain: ApolloInterceptorChain): Flow<ApolloResponse<D>> {
    return if (!hasSeenValidResponse) {
      CacheOnlyInterceptor.intercept(request, chain).onEach {
        if (it.data != null) {
          // We have valid data, we can now use the network
          hasSeenValidResponse = true
        }
      }
    } else {
      // If for some reason we have a cache miss, get fresh data from the network
      CacheFirstInterceptor.intercept(request, chain)
    }
  }
}

apolloClient.query(myQuery)
    .refetchPolicyInterceptor(cacheOnlyInterceptor)
    .watch()
    .collect {
      //
    }

✨ [New] Service.mapScalar Gradle API (#3779)

You can now use mapScalar to specify your scalar mappings:

apollo {
  // Replace 
  customScalarsMapping.set(mapOf(
      "Date" to "java.util.Date"
  ))
  
  // With
  mapScalar("Date", "java.util.Date")
}

mapScalar also works with built-in scalar types so you can map the ID type to a kotlin Long:

apollo {
  // This requires registering an adapter at runtime with `addCustomScalarAdapter()` 
  mapScalar("ID", "kotlin.Long")
}

As an optimization, you can also provide the adapter at compile time. This will avoid a lookup at runtime everytime such a scalar is read:

apollo {
  // No need to call `addCustomScalarAdapter()`, the generated code will use the provided adapter 
  mapScalar("ID", "kotlin.Long", "com.apollographql.apollo3.api.LongAdapter")
}

For convenience, a helper function is provided for common types:

apollo {
  // The generated code will use `kotlin.Long` and the builtin LongAdapter 
  mapScalarToKotlinLong("ID")

  // The generated code will use `kotlin.String` and the builtin StringAdapter
  mapScalarToKotlinString("Date")

  // The generated code will use `com.apollographql.apollo3.api.Upload` and the builtin UploadAdapter
  mapScalarToUpload("Upload")
}

🚧 [Changed] convertApolloSchema and downloadApolloSchema now use paths relative to the root of the project (#3773, #3752)

Apollo Kotlin adds two tasks to help to manage schemas: convertApolloSchema and downloadApolloSchema. These tasks are meant to be used from the commandline.

Previously, paths were interpreted using the current working directory with File(path). Unfortunately, this is unreliable because Gradle might change the current working directory in some conditions (see Gradle#13927 or Gradle#6074 for an example).

With 3.1.0 and onwards, paths, will be interpreted relative to the root project directory (project.rootProject.file(path)):

# schema is now interpreted relative to the root project directory and
# not the current working directory anymore. This example assumes there 
# is a 'app' module that applies the apollo plugin
./gradlew downloadApolloSchema \
  --endpoint="https://your.domain/graphql/endpoint" \
  --schema="app/src/main/graphql/com/example/schema.graphqls"

❤️ External contributors

Many thanks to @dhritzkiv, @mune0903, @StylianosGakis, @AchrafAmil and @jamesonwilliams for their awesome contributions! You rock 🎸 🤘 !

👷 All changes

  • Fix error reporting when there is a "schema.graphqls" but it doesn't contain any type definition (#3844)
  • Make guessNumber read the next value only once, fixes parsing custom scalars without a custom adapter (#3839, #3836)
  • Clarify need to pass client's customScalarAdapters to store methods (#3838)
  • Fix null pointer exception in LruCache while trimming (#3833)
  • Add FetchPolicy.CacheAndNetwork (#3828)
  • Allow to specify error handling for watch() (#3817)
  • Scalar mapping and adapter configuration improvements (#3779)
  • Tunnel variables in CustomScalarAdapters (#3813)
  • Terminate threads correctly if no subscription has been executed (#3803)
  • fix validation of merged fields (#3799)
  • Make reconnectWhen suspend and pass attempt number (#3772)
  • Merge HTTP headers when batching (#3776)
  • MockServer improvements and TestNetworkTransport (#3757)
  • Fix calling ApolloClient.newBuilder() if the original ApolloClient used .okHttpClient (#3771)
  • Make convertApolloSchema and downloadApolloSchema use path from the root of the project (#3773, #3752)
  • fix fragment package name in multi-module scenarios (#3775)
  • Make the error printer robust to unknown source locations, fixes schemas with duplicate types (#3753)
  • Allow to customize the fetchPolicy with interceptors (#3743)
Dec 16, 2021

This is the first stable release for Apollo Android 3 Apollo Kotlin 3 🎉!

There is documentation, a migration guide and a blog post.

In a nutshell, Apollo Kotlin 3 brings:

  • coroutine APIs for easier concurrency
  • multiplatform support makes it possible to run the same code on Android, JS, iOS, MacOS and linux
  • responseBased codegen is a new optional codegen that models fragments as interfaces
  • SQLite batching makes reading from the SQLite cache significantly faster
  • Test builders offer a simple APIs to build fake models for your tests
  • The @typePolicy and @fieldPolicy directives make it easier to define your cache ids at compile time
  • The @nonnull directive catches null values at parsing time, so you don't have to deal with them in your UI code

Feel free to ask questions by either opening an issue on our GitHub repo, joining the community or stopping by our channel in the KotlinLang Slack(get your invite here).

Changes compared to 3.0.0-rc03:

  • Fix rewinding the Json stream when lists are involved (#3727)
  • Kotlin 1.6.10 (#3723)
  • Disable key fields check if unnecessary and optimize its perf (#3720)
  • Added an easy way to log cache misses (#3724)
  • Add a Data.toJson that uses reflection to lookup the adapter (#3719)
  • Do not run the cache on the main thread (#3718)
  • Promote JsonWriter extensions to public API (#3715)
  • Make customScalarAdapters and subscriptionsNetworkTransport public (#3714)
Dec 13, 2021

Compared to the previous RC, this version adds a few new convenience API and fixes 3 annoying issues.

💙 Many thanks to @mateuszkwiecinski, @schoeda and @fn-jt for all the feedback 💙

✨ New APIs

  • Make ApolloCall.operation public (#3698)
  • Add SubscriptionWsProtocolAdapter (#3697)
  • Add Operation.composeJsonRequest (#3697)

🪲 Bug fixes

  • Allow repeated @fieldPolicy (#3686)
  • Fix incorrect merging of nested objects in JSON (#3672)
  • Fix duplicate query detection during validation (#3699)
Dec 10, 2021

💙 Many thanks to @michgauz, @joeldenke, @rohandhruva, @schoeda, @CoreFloDev and @sproctor for all the feedback 💙

⚙️ [breaking] Merge ApolloQueryCall, ApolloSubscriptionCall, ApolloMutationCall (#3676)

In order to simplify the API and keep the symmetry with ApolloRequest<D> and ApolloResponse<D>, ApolloQueryCall<D, E>, ApolloSubscriptionCall<D, E>, ApolloMutationCall<D, E> are replaced with ApolloCall<D>. This change should be mostly transparent but it's technically a breaking change. If you are passing ApolloQueryCall<D, E> variables, it is safe to drop the second type parameter and use ApolloCall<D> instead.

✨ [New] Add WebSocketNetworkTransport.reconnectWhen {} (#3674)

You now have the option to reconnect a WebSocket automatically when an error happens and re-subscribe automatically after the reconnection has happened. To do so, use the webSocketReconnectWhen parameter:

val apolloClient = ApolloClient.Builder()
    .httpServerUrl("http://localhost:8080/graphql")
    .webSocketServerUrl("http://localhost:8080/subscriptions")
    .wsProtocol(
        SubscriptionWsProtocol.Factory(
            connectionPayload = {
              mapOf("token" to upToDateToken)
            }
        )
    )
    .webSocketReconnectWhen {
      // this is called when an error happens on the WebSocket
      it is ApolloWebSocketClosedException && it.code == 1001
    }
    .build()

Better Http Batching API (#3670)

The HttpBatchingEngine has been moved to an HttpInterceptor. You can now configure Http batching with a specific method:

apolloClient = ApolloClient.Builder()
    .serverUrl(mockServer.url())
    .httpBatching(batchIntervalMillis = 10)
    .build()

All changes:

  • Add 2.x symbols (Rx2Apollo, prefetch(), customAttributes(), ApolloIdlingResource.create()) to help the transition (#3679)
  • Add canBeBatched var to ExecutionOptions (#3677)
  • Merge ApolloQueryCall, ApolloSubscriptionCall, ApolloMutationCall (#3676)
  • Add WebSocketNetworkTransport.reconnectWhen {} (#3674)
  • Move BatchingHttpEngine to a HttpInterceptor (#3670)
  • Add exposeErrorBody (#3661)
  • fix the name of the downloadServiceApolloSchemaFromRegistry task (#3669)
  • Fix DiskLruHttpCache concurrency (#3667)
Dec 7, 2021

This version is the release candidate for Apollo Android 3 🚀. Please try it and report any issues, we'll fix them urgently.

There is documentation and a migration guide. More details are coming soon. In a nutshell, Apollo Android 3 brings, amongst other things:

  • coroutine APIs for easier concurrency
  • multiplatform support makes it possible to run the same code on Android, JS, iOS, MacOS and linux
  • responseBased codegen is a new optional codegen that models fragments as interfaces
  • SQLite batching makes reading from the SQLite cache significantly faster
  • Test builders offer a simple APIs to build fake models for your tests
  • The @typePolicy and @fieldPolicy directives make it easier to define your cache ids at compile time
  • The @nonnull directive catches null values at parsing time, so you don't have to deal with them in your UI code

Compared to beta05, this version changes the default value of generateOptionalOperationVariables, is compatible with Gradle configuration cache and fixes a few other issues.

⚙️ API changes

Optional operation variables (#3648) (breaking)

The default value for the generateOptionalOperationVariables config is now true.

What this means:

  • By default, operations with nullable variables will be generated with Optional parameters
  • You will need to wrap your parameters at the call site

For instance:

query GetTodos($first: Int, $offset: Int) {
  todos(first: $first, offset: $offset) {
    ...Todo
  }
}
// Before
val query = GetTodosQuery(100, null)

// After
val query = GetTodosQuery(Optional.Present(100), Optional.Absent)

  • If you prefer, you can set generateOptionalOperationVariables to false to generate non-optional parameters globally
  • This can also be controlled on individual variables with the @optional directive
  • More information about this can be found here

We think this change will make more sense to the majority of users (and is consistent with Apollo Android v2's behavior) even though it may be more verbose, which is why it is possible to change the behavior via the generateOptionalOperationVariables config.

To keep the beta05 behavior, set generateOptionalOperationVariables to false in your Gradle configuration:

apollo {
  generateOptionalOperationVariables.set(false)
}

ApolloClient.Builder improvements (#3647)

You can now pass WebSocket related options to the ApolloClient.Builder directly (previously this would have been done via NetworkTransport):

// Before
val apolloClient = ApolloClient.Builder()
    // (...)
    .subscriptionNetworkTransport(WebSocketNetworkTransport(
        serverUrl = "https://example.com/graphql",
        idleTimeoutMillis = 1000L,
        wsProtocol = SubscriptionWsProtocol.Factory()
    ))
    .build()

// After
val apolloClient = ApolloClient.Builder()
    // (...)
    .wsProtocol(SubscriptionWsProtocol.Factory())
    .webSocketIdleTimeoutMillis(1000L)
    .build()

Upgrade to OkHttp 4 (#3653) (breaking)

This version upgrades OkHttp to 4.9.3 (from 3.12.11). This means Apollo Android now requires Android apiLevel 21+. As OkHttp 3 enters end of life at the end of the year and the vast majority of devices now support apiLevel 21, we felt this was a reasonable upgrade.

🪲 Bug fixes

  • Fixed an issue where it was not possible to restart a websocket after a network error (#3646)
  • Fixed an issue where Android Java projects could not use the Apollo Gradle plugin (#3652)

👷 All Changes

  • Update a few dependencies (#3653)
  • Fix Android Java projects (#3652)
  • Expose more configuration options on ApolloClient.Builder (#3647)
  • Fix restarting a websocket after a network error (#3646)
  • Change optional default value (#3648)
  • Fix configuration cache (#3645)
Dec 3, 2021

We intended this version to be the first release candidate but we found a few bugs justifying a new beta instead. Many thanks to @CoreFloDev, @olivierg13, @rohandhruva, @adavis and @sproctor for identifying the bugs and sending feedback, this helps a ton 🙏 💙 . Next one should be release candidate 🤞 🚀

🪲 Bug fixes

  • Fix a crash when using Auto Persisted Queries on iOS (#3637)
  • Fix a crash that could happen when saving an object to the cache after some types of schema changes (#3636)
  • Escape reserved enum value names ("name" and "ordinal") (#3629)
  • Do not crash when trying to send to a closed websocket (#3638)

🖋️ Better escaping for Kotlin codegen

The escaping of Kotlin keywords used in field names now uses Kotlin's backtick escaping (`keyword`) convention instead of suffixing an underscore (keyword_). This could impact the generated code in your projects if a schema contains such fields. (#3630)

⚙️ [breaking] API changes

Before going stable, the public API has been tweaked to ensure consistency.

Uniformize Upload creation (#3613)

Creating an Upload used to be possible by calling Upload.fromXyz() methods and now has been changed to use a Builder API which is more consistent with the rest of the API:

// Before
val upload = Upload.fromSource(okioSource)
// or if you're on the JVM
val upload = Upload.fromFile(file)

// After
val upload = DefaultUpload.Builder()
    .content(bufferedSource)
    // or if you're on the JVM
    .content(file)
    .build()

Simplify Automatic Persisted Queries configuration (#3622)

The method to enable or disable Automatic Persisted Queries on a specific call has been renamed to a more meaningful name:

// Before
val response = apolloClient.query(HeroNameQuery())
    .hashedQuery(false)
    .execute()

// After
val response = apolloClient.query(HeroNameQuery())
    .enableAutoPersistedQueries(false)
    .execute()

Remove temporarily deprecated methods (#3632)

During the course of the alpha and beta, a number of APIs have been evolving, and to allow a smooth transition by early adopters, some early APIs have been marked as deprecated in favor of new ones.

Now that we are soon reaching a stable API, it is time to remove these early APIs.

This means if you were using 3.0.0-beta04 or earlier you may want to first review the deprecation warnings and update your code to use the newer APIs, before upgrading to this version.

Other changes

  • Should you need to create an ApolloResponse, you can now do so by using a Builder API (#3608)
  • Ast, Compiler and MockServer modules have been marked as experimental to reflect the fact that they haven't been widely used as of now and therefore their APIs could evolve in the future (#3614)
  • Some overloads were removed in APIs related to JSON parsing/writing to simplify these APIs. Now all of these APIs take an Okio BufferedSource as input. (#3620)
  • The actual implementations of HttpEngine (OkHttpEngine, NSURLSessionHttpEngine, and KtorHttpEngine) have all been renamed to DefaultHttpEngine for consistency (#3617)

☕️ Java Interop improvements

  • The built-in adapters are now usable in a more Java-friendly way (#3607):
// Before
String result = StringAdapter.INSTANCE.fromJson(jsonReader,CustomScalarAdapters.Empty);

// After
String result = Adapters.StringAdapter.fromJson(jsonReader,CustomScalarAdapters.Empty);
  • Correctly declare throwing IOException in Adapter APIs, making it possible to catch it in Java code (#3603)

👷 All Changes

  • Do not crash when trying to send to a closed websocket (#3638)
  • URLEncode more characters for NSURL to not crash (#3637)
  • Fix collecting fields on new schema types (#3636)
  • Remove temporarily deprecated methods (#3632)
  • Differentiate escaping of Java or Kotlin keywords (#3630)
  • Escape reserved enum value names ("name" and "ordinal") (#3629)
  • Move the HTTP cache to a HttpInterceptor (#3624)
  • Simplify APQ configuration (#3622)
  • Cleanup overloads (#3620)
  • 🙅 No constructor fun (#3617)
  • Mark Ast, Compiler and MockServer modules as experimental (#3614)
  • Uniformize Upload creation (#3613)
  • 🧼 🪣 Polishing of the ApolloResponse API (#3608)
  • Make builtin scalar adapters easier to call from Java (#3607)
  • Fix CustomTypeAdapter compat (#3604)
  • Use kotlin.Throws (#3603)
Nov 24, 2021

This version is planned to be the last beta of Apollo Android 3 before a release candidate 🚀. It mainly focuses on stabilization and tweaks to the public API, makes it easier to work with the Java9 module system, and fixes a few issues.

⚙️ [breaking] API changes

Before going stable, we tweaked the public API to ensure consistency. The following methods have been renamed:

BeforeAfter
ApolloClient.mutate()ApolloClient.mutation()
ApolloClient.subscribe()ApolloClient.subscription()
ApolloSubscriptionCall.execute()ApolloSubscriptionCall.toFlow()

In order to better support Java 9 and remove split packages, the following classes have been moved:

BeforeAfter
com.apollographql.apollo3.cache.ApolloCacheHeaderscom.apollographql.apollo3.cache.normalized.api.ApolloCacheHeaders
com.apollographql.apollo3.cache.CacheHeaderscom.apollographql.apollo3.cache.normalized.api.CacheHeaders
com.apollographql.apollo3.cache.CacheHeaders.Buildercom.apollographql.apollo3.cache.normalized.api.CacheHeaders.Builder
com.apollographql.apollo3.cache.CacheHeaders.Companioncom.apollographql.apollo3.cache.normalized.api.CacheHeaders.Companion
com.apollographql.apollo3.cache.normalized.CacheKeycom.apollographql.apollo3.cache.normalized.api.CacheKey
com.apollographql.apollo3.cache.normalized.CacheKey.Companioncom.apollographql.apollo3.cache.normalized.api.CacheKey.Companion
com.apollographql.apollo3.cache.normalized.CacheKeyResolvercom.apollographql.apollo3.cache.normalized.api.CacheKeyResolver
com.apollographql.apollo3.cache.normalized.CacheResolvercom.apollographql.apollo3.cache.normalized.api.CacheResolver
com.apollographql.apollo3.cache.normalized.DefaultCacheResolvercom.apollographql.apollo3.cache.normalized.api.DefaultCacheResolver
com.apollographql.apollo3.cache.normalized.FieldPolicyCacheResolvercom.apollographql.apollo3.cache.normalized.api.FieldPolicyCacheResolver
com.apollographql.apollo3.cache.normalized.NormalizedCache.Companioncom.apollographql.apollo3.cache.normalized.api.NormalizedCache.Companion
com.apollographql.apollo3.cache.normalized.NormalizedCacheFactorycom.apollographql.apollo3.cache.normalized.api.NormalizedCacheFactory
com.apollographql.apollo3.cache.normalized.ReadOnlyNormalizedCachecom.apollographql.apollo3.cache.normalized.api.ReadOnlyNormalizedCache
com.apollographql.apollo3.cache.normalized.Recordcom.apollographql.apollo3.cache.normalized.api.Record
com.apollographql.apollo3.cache.normalized.RecordFieldJsonAdaptercom.apollographql.apollo3.cache.normalized.api.RecordFieldJsonAdapter
com.apollographql.apollo3.cache.normalized.MemoryCachecom.apollographql.apollo3.cache.normalized.api.MemoryCache
com.apollographql.apollo3.cache.normalized.MemoryCacheFactorycom.apollographql.apollo3.cache.normalized.api.MemoryCacheFactory
com.apollographql.apollo3.cache.normalized.OperationCacheExtensionsKtcom.apollographql.apollo3.cache.normalized.api.OperationCacheExtensionsKt

Other renames

BeforeAfter
com.apollographql.apollo3.api.ApolloExperimentalcom.apollographql.apollo3.annotations.ApolloExperimental
com.apollographql.apollo3.api.ApolloInternalcom.apollographql.apollo3.annotations.ApolloInternal
com.apollographql.apollo3.cache.normalized.ObjectIdGeneratorcom.apollographql.apollo3.cache.normalized.api.CacheKeyGenerator
com.apollographql.apollo3.cache.normalized.ObjectIdGeneratorContextcom.apollographql.apollo3.cache.normalized.api.CacheKeyGeneratorContext
com.apollographql.apollo3.network.http.OkHttpEnginecom.apollographql.apollo3.network.http.DefaultHttpEngine

Renames in ApolloCall and ApolloClient (#3594)

ApolloClient's mutate and subscribe methods have been renamed to mutation and subscription respectively. This creates a more consistent API by reusing the GraphQL specification terminology. Also, mutate(), subscribe() could be misleading as despite being verbs, no action is done until execute() is called. Using mutation()/subscription() hints that execute() is required as a terminal operation.

// Before
val data = client.mutate(MyMutation()).execute()

// After
val data = client.mutation(MyMutation()).execute()

Also, ApolloSubscriptionCall.execute() has been renamed to ApolloSubscriptionCall.toFlow() to make it clearer that it returns a cold Flow that can be collected several times:

// Before
val flow = client.subscription(MySubscription).execute()

// After
val flow = client.subscription(MySubscription).toFlow()

Rename ObjectId to CacheKey (#3575)

CacheKeys were sometimes referred to as Cache ids, Object ids, despite all of them being the same concept (the key of an object in the cache). For consistency inside the Apollo Android codebase and with the iOS and Web clients, CacheKey is going to be used everywhere moving forward

// Before
val objectIdGenerator = object : ObjectIdGenerator {
  override fun cacheKeyForObject(obj: Map<String, Any?>, context: ObjectIdGeneratorContext): CacheKey? {
    // Retrieve the id from the object itself
    return CacheKey(obj["id"] as String)
  }
}

val apolloClient = ApolloClient.Builder()
    .serverUrl("https://...")
    .normalizedCache(
        normalizedCacheFactory = cacheFactory,
        objectIdGenerator = objectIdGenerator,
    )
    .build()

// After
val cacheKeyGenerator = object : CacheKeyGenerator {
  override fun cacheKeyForObject(obj: Map<String, Any?>, context: CacheKeyGeneratorContext): CacheKey? {
    // Retrieve the id from the object itself
    return CacheKey(obj["id"] as String)
  }
}

val apolloClient = ApolloClient.Builder()
    .serverUrl("https://...")
    .normalizedCache(
        normalizedCacheFactory = cacheFactory,
        cacheKeyGenerator = cacheKeyGenerator,
    )
    .build()

dataAssertNoErrors (#3534)

To reflect more accurately what this method does, dataOrThrow is now dataAssertNoErrors.

Kotlin enums generation by default (#3551)

Apollo Android can either generate GraphQL enums as Kotlin enums or sealed classes. In previous 3.0 releases, the default was to generate sealed classes but it is now enums, as this will be more expected, works better with Swift, and is consistent with the 2.x behavior. To fallback to the previous behaviour of always generating sealed classes, use the sealedClassesForEnumsMatching Gradle option:

apollo {
  sealedClassesForEnumsMatching.set(listOf(".*"))
}

Improve java usability (#3576 and #3509)

Using Apollo Android from Java should be less cumbersome thanks to a few API tweaks in this release. For example, to set up a normalized cache:

// Before
builder = ClientCacheExtensionsKt.normalizedCache(
    builder,
    new MemoryCacheFactory(10 * 1024 * 1024, -1),
    TypePolicyObjectIdGenerator.INSTANCE,
    FieldPolicyCacheResolver.INSTANCE,
    false
    );

// After
    NormalizedCache.configureApolloClientBuilder(
    builder,
    new MemoryCacheFactory(10 * 1024 * 1024, -1),
    );

Other noteworthy tweaks (#3509, #3538, #3557, #3598)

  • HttpNetworkTransport and WebSocketNetworkTransport now use the Builder pattern for instantiation
  • FlowDecorator have been removed from ApolloClient as ApolloInterceptor can be used instead
  • httpHeader extension is now addHttpHeader
  • We also made sure with this release that classes that are not meant to be used outside the library are marked as internal.

✨ [new] Java Platform Module System (JPMS) support (#3558)

Apollo Android now publishes jar files containing the Automatic-Module-Name property and non-split packages. This change makes it easier to work with Apollo Android from a Java 9+ modularized application.

✨ [new] Add fallback adapters (#3582)

You no longer need to register any of the built-in adapters after declaring a custom scalar type that maps to a built-in type (Int, Long, String, etc.).

For instance, when declaring this custom scalar type:

apollo {
  customScalarsMapping = [
      "MyCustomNumber": "kotlin.Long"
  ]
}

There is no need to call addCustomScalarAdapter on the builder anymore, as since it is for a kotlin.Long it will automatically fall back to the built-in LongAdapter.

🪲 [fixes] Optimistic cache and Gradle plugin (#3577, #3496)

  • Fix an issue where the optimistic cache data was not reverted when an error happens
  • The Gradle plugin now correctly works without the Kotlin Plugin

👷 All Changes

  • make Guard and NonMainWorker internal (#3600)
  • Tweak visibility of apollo-api symbols (#3598)
  • Make a few classes internal (#3591)
  • Make a few classes either internal or @ApolloInternal (#3590)
  • Check internal on http-cache (#3589)
  • Renames in ApolloCall and ApolloClient (#3594)
  • Add apollo-annotations (#3588)
  • Add fallback adapters (#3582)
  • Merge main in dev-3.x (#3580)
  • Improve java usability (#3576)
  • Optimistic cache fixes (#3577)
  • Add 2.x compat mode for useHttpGetMethodForQueries and useHttpGetMethodForPersistedQueries (#3578)
  • Rename ObjectId to CacheKey (#3575)
  • Handle experimental opt ins (#3565)
  • Kotlin 1.6 (#3563)
  • JPMS support (#3558)
  • Rename httpHeader extensions to addHttpHeader (#3557)
  • Go back to generating GraphQL enums as Kotlin enums by default (#3551)
  • Relocate the kotlin-stdlib in the Gradle Plugin (#3542)
  • Add "UI testing" and remove flowDecorators (#3538)
  • Allow Delete/Continue/Replace during AST transformations (#3545)
  • Tweak the errors doc and add ApolloResponse.dataAssertNoErrors (#3534)
  • update Gradle and wrapper to 7.3 (#3535)
  • Mark Test Builders are experimental (#3511)
  • Add more Builders and other API tuning ✨ (#3509)
  • Make the Gradle plugin work without the Kotlin Plugin (#3496)
Nov 12, 2021

Version 2.5.11 is a maintenance release with fixes for registerApolloOperations and query batching.

💜 Many thanks to @AOrobator and @rocketraman for their awesome contributions and feedback !💜

👷 All Changes

Full Changelog: https://github.com/apollographql/apollo-android/compare/v2.5.10...v2.5.11

Nov 5, 2021

3.0.0-beta03 is a hotfix release to fix an issue where the SQLite cache could trigger SQLiteDatabaseLockedException under load (#3503). If you're using the SQLite cache, we strongly recommend updating.

👷 All Changes

  • Fix concurrent accesses to the SQLite database (#3503)
Nov 3, 2021

This version introduces fluent APIs on operations, more multiplatform targets, more compatibility helpers to ease transition from v2.x, and other fixes.

✨ [new and breaking] Fluent APIs (#3447)

The API to execute operations now uses the Fluent style, where you can chain method calls together. The ApolloClient query, mutate and subscribe methods now return an ApolloCall that can be configured, and on which you can call execute to execute the operation.

Before:

val request = ApolloRequest.Builder(HeroQuery()).fetchPolicy(FetchPolicy.NetworkOnly).build()
val response = apolloClient.query(request)

After (fluent):

val response = apolloClient.query(HeroQuery())
        .fetchPolicy(FetchPolicy.NetworkOnly)
        .execute()

If you were using cacheAndNetwork, it's now executeCacheAndNetwork:

// Before
apolloClient.cacheAndNetwork(query)

// After
apolloClient.query(query).executeCacheAndNetwork()

✨ [new] New multiplatform targets (#3487)

This release adds support for these new targets:

  • macosArm64
  • iosArm64
  • iosSimulatorArm64
  • watchosArm64
  • watchosSimulatorArm64
  • tvosArm64
  • tvosX64
  • tvosSimulatorArm64

Additionally, apollo-api (models) is now compiled for linuxX64. Follow this issue for updates on this - and contributions on this area are welcome!

✨ [new] Sealed interfaces in generated code (#3448)

When using the responseBased codegen, now interfaces will be generated as sealed when possible.

Because sealed interfaces are only available since Kotlin 1.5, a new Gradle option languageVersion has been introduced to control this behavior (and potentially future language features used by the codegen).

✨ [new] Version 2 compatibility helpers

To ease transition from 2.x, this release provides:

  • a CustomTypeAdapter that can be used for custom scalars in the same way as in 2.x
  • an Input helper that will map to 3.x Optional automatically

These helpers are deprecated and are to be used only as a temporary measure - they will be removed in a future version.

💙 External contributors

Thanks to @davidec-twinlogix, @pauldavies83 @nachtien and @Pitel for their awesome contributions!

👷 All Changes

  • Add a "compat" v2 CustomTypeAdapter to v3 Adapter API (#3482)
  • Add a few targets (#3487)
  • Add parsing of non-standard Error fields (#3485)
  • Resolve name clashes the same way as in 2.x (#3486)
  • Make the packageName error more explicit for users that are migrating (#3477)
  • Fix pointer use after free (#3478)
  • Add Apple Silicon iOS Simulator Targets (#3481)
  • Check minimum Kotlin version in the plugin (#3475)
  • Migrate HttpRequest With-ers to Builders (#3474)
  • Add .customScalarAdapter(CustomScalarAdapters) on ApolloClient.Builder (#3472)
  • Bump KSP and Okio to stable 🎉 (#3473)
  • [RFC] Fluent query API (#3447)
  • Generate sealed interfaces in response based codegen (#3448)
  • Add MySealedClass.knownValues() (#3465)
  • Added Long support to RecordFieldJsonAdapter (#3468)
  • Add an iOSX64 test and publish apollo-testing-support for ios (#3463)
  • Add customTypesMapping for backward compatibility (#3452)
  • Add Input backward compat layer (#3456)
  • Rename include to includes (#3451)
Oct 25, 2021

Version 3.0.0-beta01 is the first Beta release for Apollo Android 3 🎉. While there are no API stability guarantees just yet, 3.0.0-beta01 introduces binary compatibility validation to monitor the breaking changes and they should happen less frequently from now on.

One important API change in 3.0.0-beta01 is the change from with-ers to Builders. It also has a useVersion2Compat Gradle property to ease the transition from 2.x.

In addition, 3.0.0-beta01 introduces JavaScript runtime and cache support and new Test Builders APIs to generate fake data models.

💜 Many thanks to @Pitel and @dchappelle for the awesome additions to this release !💜

✨[new] JavaScript runtime and cache support (#3208)

Version 3.0.0-beta01 has support for JavaScript targets courtesy of @Pitel. It contains both IR and LEGACY artifacts. To use it in your builds, add apollo-runtime and apollo-normalized-cache dependencies to your build.gradle[.kts]:

kotlin {
  js(IR) { // or js(LEGACY)
    sourceSets {
      val commonMain by getting {
        // To use HTTP and runtime 
        implementation("com.apollographql.apollo3:apollo-runtime:3.0.0-beta01")
        // To use in-memory cache
        implementation("com.apollographql.apollo3:apollo-normalized-cache:3.0.0-beta01")
      }
    }
  }
}

This is everything needed. All the APIs work the same as their equivalent JVM/native ones.

Two caveats:

  • SQLite is not supported yet (see #3442)
  • WebSocket are not supported yet (see #3443)

Contributions are very welcome, feel free to reach out on the kotlin lang slack to get started!

✨[new] Test Builders API (#3415)

You can now opt-in generation of Test Builders that make it easier to build fake models for your operations. Test Builders allow to generate fake data using a type safe DSL and provides mock values for fields so that you don't have to specify them all.

To enable Test Builders, add the below to your Gradle scripts:

apollo {
  generateTestBuilders.set(true)
}

This will generate builders and add them to your test sourceSets. You can use them to generate fake data:

// Import the generated TestBuilder
import com.example.test.SimpleQuery_TestBuilder.Data

@Test
fun test() {
  // Data is an extension function that will build a SimpleQuery.Data model
  val data = SimpleQuery.Data {
    // Specify values for fields that you want to control
    hero = droidHero {
      name = "R2D2"
      friends = listOf(
          friend {
            name = "Luke"
          }
      )
      // leave other fields untouched, and they will be returned with mocked data
      // planet = ...
    }
  }
  
  // Use the returned data
}

You can control the returned mock data using the TestResolver API:

val myTestResolver = object: DefaultTestResolver() {
  fun resolveInt(path: List<Any>): Int {
    // Always return 42 in fake data for Int fields
    return 42
  }
}

val data = SimpleQuery.Data(myTestResolver) {}
// Yay, now every Int field in `data` is 42!

✨🚧✨ [new and breaking] Version2 compatibility

3.0.0-beta01 introduces new Gradle options for better compatibility with versions 2. Most of the changes in this section can be reverted through configuration options but some had breaking side effects like valueOf being renamed to safeValueOf for enums.

sealedClassesForEnumsMatching allows generating Kotlin enums for GraphQL enums.

Apollo 3.x generates sealed classes for Kotlin enums. As paradoxical as it may seem, sealed classes a better representation of GraphQL enums because they allow to expose the rawValue of new enums that are not know at compile time. Sealed classes can also handle when exhaustivity just like Kotlin enums and are generally more flexible. Using them may change the calling code though so as a temporary migration helper, you can now fallback to enum like in 2.x by setting sealedClassesForEnumsMatching to an empty list instead of the default listOf(".*"):

apollo {
  sealedClassesForEnumsMatching.set(emptyList())
}

One side effect of this change is that the generated MySealedClass.valueOf() has been renamed to MySealedClass.safeValueOf().

generateOptionalOperationVariables allows wrapping your variables in Optional<>.

By default Apollo Android 3 skips the Optional<> wrapper for nullable variables. This simplifies the call site in the vast majority of cases where the variable is actually sent alongside the query.

There might be some rare occasions where you want to be able to omit a variable. For these cases, you can add an @optional directive:

# a query that allows omitting before and/or after for bi-directional pagination
query MyQuery($before: String @optional, $after: String @optional) {
  items {
    title
  }
}

If you have a lot of those queries, or if you prefer the 2.x behaviour, you can now opt-out globally:

apollo {
  generateOptionalOperationVariables.set(true)
}

codegenModels defaults to "operationBased"

3.0.0-beta01 now defaults to "operationBased" models. "operationBased" models match your GraphQL operations 1:1 and skip the extra .fragment synthetic fields that are present in "compat" models. Because they are simpler to understand, generate and execute, they are now the default. You can revert to "compat" codegen with the codegenModels Gradle option:

apollo {
  codegenModels.set(MODELS_COMPAT)
}

useVersion2Compat()

For all these options, you can now fallback to the 2.x behaviour with useVersion2Compat(). This is a shorthand function that configures the above options to match the 2.x behaviour. useVersion2Compat is a helper to facilitate the migration and will be removed in a future update.

🚧[breaking] ApolloClient and ApolloRequest Builder APIs

Following the with-er vs Builder vs DSL RFC, we decided to move the main APIs to Builders. Builders are widely accepted, battle proven APIs that play nicely with Java and will make it easier to maintain Apollo Android in the long run.

While this beta-01 release keeps the with-ers, they will be removed before Apollo Android 3 goes stable so now is a good time to update.

To build an ApolloClient:

// Replace
val apolloClient = ApolloClient("https://com.example/graphql")
    .withNormalizedCache(normalizedCacheFactory)

// With
val apolloClient = ApolloClient.Builder()
    .serverUrl("https://com.example/graphql")
    .normalizedCache(normalizedCacheFactory)
    .build()

To build a ApolloRequest:

// Replace
val apolloRequest = ApolloRequest(query)
    .withFetchPolicy(FetchPolicy.CacheFirst)

// With
val apolloRequest = ApolloRequest.Builder(query)
    .fetchPolicy(FetchPolicy.CacheFirst)
    .build()

Websocket updates

The WebSocket code has been revamped to support client-initiated ping-pong for graphql-ws as well as a better separation between common code and protocol specific code.

A side effect is that WebSocket protocols are now configured using a WsProtocol.Factory:

// Replace
val apolloClient = ApolloClient(
    networkTransport = WebSocketNetworkTransport(
        serverUrl = "http://localhost:9090/graphql",
        protocol = GraphQLWsProtocol()
    )
)

// With
val apolloClient = ApolloClient.Builder()
    .networkTransport(
        WebSocketNetworkTransport(
            serverUrl = "http://localhost:9090/graphql",
            protocolFactory = GraphQLWsProtocol.Factory()
        )
    )
    .build()

👷 All Changes

New Contributors

Full Changelog: https://github.com/apollographql/apollo-android/compare/v3.0.0-alpha07...v3.0.0-beta01

Oct 15, 2021

Version 2.5.10 is a maintenance release with a few bugfixes, mutiny support and a new Gradle register${VariantName}ApolloOperations task to register your operations to the Apollo registry.

💜 Many thanks to @ProVir, @aoudiamoncef and @jgarrow for their contributions !💜

✨[new] Mutiny support (#3213)

Version 2.5.10 adds support for the Mutiny reactive library.

Add the dependency:

// Mutiny support
implementation 'com.apollographql.apollo:apollo-mutiny-support:x.y.z'

And convert your ApolloCall to a Mutiny Uni:

// Create a query object
val query = EpisodeHeroNameQuery(episode = Episode.EMPIRE.toInput())

// Directly create Uni with Kotlin extension
val uni = apolloClient.mutinyQuery(query)

Read more in the documentation

✨[new] register${VariantName}ApolloOperations (#3403)

If you're using Apollo safelisting, you can now upload the transformed operations from Gradle directly. Add a registerOperations {} block to the apollo {} block:

apollo {
  service("service") {
    registerOperations {
      // You can get your key from https://studio.apollographql.com/graph/$graph/settings
      key.set(System.getenv("APOLLO_KEY"))
      graph.set(System.getenv("APOLLO_GRAPH"))
      // Use "current" by default or any other graph variant
      graphVariant.set("current")
    }
  }
}

Then call ./gradlew registerMainServiceApolloOperations to register your operations to the registry. The operations will be registered including the added __typename fields that might be added during codegen.

👷 All Changes

New Contributors

Full Changelog: https://github.com/apollographql/apollo-android/compare/v2.5.9...v2.5.10

Sep 30, 2021

Version 3.0.0-alpha07 has new builtin Adapters for java.time, better custom scalar handling for multi module projects and multiple fixes for MPP and json parsers.

💜 Many thanks to @ProVir, @ychescale9 and @pauldavies83 for all the feedback and investigations !💜

✨[new] Adapters for java.time

Like there were adapters for kotlinx.datetime, you can now use java.time adapters from the apollo-adapters package:

  • com.apollographql.apollo3.adapter.JavaInstantAdapter
  • com.apollographql.apollo3.adapter.JavaLocalDateAdapter
  • com.apollographql.apollo3.adapter.JavaLocalDateTimeAdapter

🚧[breaking] renamed kotlinx.datetime Adapters

To keep things symmetrical with the java.timeadapters, the kotlinx.datetime adapters are now named:

  • com.apollographql.apollo3.adapter.KotlinxInstantAdapter
  • com.apollographql.apollo3.adapter.KotlinxLocalDateAdapter
  • com.apollographql.apollo3.adapter.KotlinxLocalDateTimeAdapter

🚧[breaking] new LongAdapter package name

Because LongAdapter is widely used and doesn't require any additional dependencies, it is now in the builtin apollo-api package

// Replace
com.apollographql.apollo3.adapter.LongAdapter

// With
com.apollographql.apollo3.api.LongAdapter

👷 All Changes

  • Fix OutOfBoundsException in MapJsonReader (#3375)
  • Add Long support to JsonReader and JsonWriter (#3370)
  • Fix structured concurrency issues dur to runCatching (#3364)
  • add a convenience ApolloClient.apolloStore (#3367)
  • simplify scalar handling (#3362)
  • add java.time Adapters (#3360)
  • relax freezing restrictions on the store (#3358)
Sep 15, 2021

Hotfix release to fix Android Studio autocomplete for generated files (#3354)

Sep 14, 2021

3.0.0-alpha05 is an incremental update on top of alpha04 with a few important fixes. Many thanks to all the feedback, in particular from @rickclephas, @ProVir, @ychescale9 and @james on the Kotlinlang slack. Thank you 💜!

Relaxed freezing restrictions (#3350)

The Kotlin Native runtime was too strict in ensuring the Continuations were never frozen, which caused exceptions when used with coroutines-native-mt. This check has been removed.

Add ApolloClient.clearNormalizedCache (#3349)

To ease the transition from 2.x, clearNormalizedCache has been added as a deprecated function and will be removed for 3.1

Introduce __Schema to hold the list of all types (#3348)

Because only used types are generated in alpha04, we lost the ability to compute possible types in a typesafe way. This PR re-introduces this with an opt-it generateSchema Gradle property:

apollo {
  generateSchema.set(true)
}

This will:

  1. Force generate all composite types (interfaces, objects, unions)
  2. generate a __Schema class that will hold a list of all composite types

The __Schema class has a possibleTypes() function that can lookup possible types from the list of types:

    assertEquals(
        setOf(Cat.type, Dog.type, Crocodile.type),
        __Schema.possibleTypes(Animal.type).toSet()
    )

Fix custom scalars in multi module projects (#3341)

For multi-project builds, custom scalar were registered twice, leading to a validation error. This is now fixed.

Sep 7, 2021

3.0.0-alpha04 brings Java codegen and ApolloIdlingResource in Apollo Android 3. These were the final pieces that were not ported to Apollo Android 3 yet 🎉.. In addition, it brings some consistency improvements in the codegen/Gradle setup as well bump the max json stack size to 256 and other ergonomic improvements and fixes.

As we move towards a stable version, we'll have to settle for the current with-er API or Builders. Let us know what you think by letting a comment on the RFC.

✨[new] Java Codegen

Apollo Android 3 now generates Java models if the Kotlin plugin is not applied in your project. This is detected automatically and should work in the vast majority of cases. If you want to force Java codegen while still using the Kotlin plugin, you can set generateKotlinModels.set(false) in your Gradle scripts:

apollo {
  // Generate Java classes regardless of whether the Kotlin plugin is applied
  generateKotlinModels.set(false)
}

Java models work like Kotlin models and use the same runtime. With the exception of default parameters, they are compatible with Kotlin operationBased models so you can change the target language without having to update most of your code.

✨[new] apollo-idling-resource

apollo-idling-resource is now available in Apollo Android 3. You can include it with the apollo-idling-resource artifact:

dependencies {
  testImplementation("com.apollographql.apollo3:apollo-idling-resource:$version")
}

And create an ApolloClient that will update the IdlingResource:

val idlingResource = ApolloIdlingResource("test")
val apolloClient = ApolloClient("https://...")
    .withIdlingResource(idlingResource)

✨[new] valueOf for enums

Many thanks to @kubode for the contribution 💜

For GraphQL enums generated as sealed class, you can now call valueOf to parse a String to a given enum value (or Unknown__ if unknown):

Given the below enum:

enum Direction {
  NORTH,
  EAST,
  SOUTH,
  WEST
}

You can use:

assertEquals(Direction.NORTH, Direction.valueOf("NORTH"))
assertEquals(Direction.Unknown__("NORTH-WEST"), Direction.valueOf("NORTH-WEST"))

✨[new] Platform errors for ApolloNetworkError

Many thanks to @ProVir for the help getting this done 💜

When something wrong happens during a network request, the runtime will throw a ApolloNetworkError exception. You can now access the underlying platform error with ApolloNetworkError.platformCause:

// On iOS you can cast to NSError
try {
  apolloClient.query(query)
} catch (e: ApolloNetworkError) {
  when ((e.platformCause as NSError?)?.domain) {
    NSURLErrorDomain -> // handle NSURLErrorDomain errors ...
  }
}

// On Android/JVM, platformCause is the same Throwable as Cause:
try {
  apolloClient.query(query)
} catch (e: ApolloNetworkError) {
  when (e.cause) {
    is UnknownHostException -> // handle UnknownHostException errors ...
  }
}

🚧[breaking] Mandatory package name

Because the default of an empty package name creates issues with kapt, it is now mandatory to set the package name of generated models:

apollo {
  // Use the same packageName for all generated models
  packageName.set("com.example")
  // Or use a package name derived from the file location
  packageNamesFromFilePaths()
}

🚧[breaking] Schema Types

For consistency, and in order to support multi-modules scenarios where some types are only generated in some modules, custom scalars, object and interface types are now generated as top-level classes in the .type package name, like input objects and enums instead of being wrapped in a Types object. Each schema type has a static type: CompiledType property so you can access the type name in a type safe way.

// Replace
Types.Launch.name

// With 
Launch.type.name

If you were using generated CustomScalarType instances to register your custom scalar adapters, these are moved to the top level as well:

// Replace
ApolloClient("https://").withCustomScalarAdapter(Types.GeoPoint, geoPointAdapter)

// With
ApolloClient("https://").withCustomScalarAdapter(GeoPoint.type, geoPointAdapter)

🚧[breaking] Removed DefaultHttpRequestComposerParams

Because DefaultHttpRequestComposerParams was setting multiple parameters at the same time (HTTP headers, method, etc..), setting one of them would overwrite a default set on the client. Instead of using DefaultHttpRequestComposerParams, set parameters individually:

apolloClient.withHttpHeader("name", "value")
apolloRequest.withHttpHeader("otherName", "otherValue")

👷 All Changes

  • Input array coercion (#3337)
  • Java Codegen (#3335)
  • bump the max stack size to 256 (#3333)
  • Adds valueOf to the enum types (#3328)
  • Update to AGP 4.2 (#3324)
  • bump Kotlin (#3319)
  • Expose NSError (#3315)
  • fix visibility of presentIfNotNull. (#3316)
  • Add apollo-rx3-support (#3303)
  • Move ApolloIdlingResource to apollo-idling-resource (#3302)
  • simplify the Android setup (#3293)
  • Make it possible to override ExecutionContext properties individually (#3294)
  • Mandate package name (#3295)
  • add more diagnostics for normalization failures (#3286)
  • allow multiple HTTP headers with the same name (#3287)
  • add error.path and error.extensions (#3285)
  • Add 3.x ApolloIdlingResource (#3282)
  • bump kotlin-compile-testing and enable warningsAsErrors again (#3278)
  • add a way to disable Kdoc during development (#3279)
  • Minor Cleanups (#3277)
  • Simplify output dir connection (#3276)
  • Remove split packages (#3275)
  • Remove deprecated modules and move tests to the root level (#3274)
  • Encode defaultValue as GraphQL value in introspection schemas (#3272)
  • add a DefaultHttpNetworkTransport overload that takes a serverUrl (#3273)
Jul 26, 2021

3.0.0-alpha03 is a release with a bunch of bugfixes and documentation fixes.

The Kdoc for the current dev-3.x version is now available at: https://apollographql.github.io/apollo-android/kdoc/

👷‍ Bugfixes

  • Publish Kdoc (#3250)
  • Fix circular references when reading the cache (#3263)
  • Websocket closed error missing from graphql-ws subscription (#3257)

❤️ External contributors

Many thanks to @dchapelle for the work on WebSockets and @PHPirates and @omaksymov for the documentation fixes!

Jul 19, 2021

This version reworks the CacheResolver API to hopefully clarify its usage as well as allow more advanced use cases if needed.

ObjectIdGenerator and CacheResolver APIs

This version splits the CacheResolver API in two distinct parts:

  • ObjectIdGenerator.cacheKeyForObject
    • takes Json data as input and returns a unique id for an object.
    • is used after a network request
    • is used during normalization when writing to the cache
  • CacheResolver.resolveField
    • takes a GraphQL field and operation variables as input and generates data for this field
    • this data can be a CacheKey for objects but it can also be any other data if needed. In that respect, it's closer to a resolver as might be found in apollo-server
    • is used before a network request
    • is used when reading the cache

Previously, both methods were in CacheResolver even if under the hood, the code path were very different. By separating them, it makes it explicit and also makes it possible to only implement one of them.

Note: In general, prefer using @typePolicy and @fieldPolicy that provide a declarative/easier way to manage normalization and resolution.

ObjectIdGenerator is usually the most common one. It gives objects a unique id:

type Product {
  uid: String!
  name: String!
  price: Float!
}
// An ObjectIdGenerator that uses the "uid" property if it exists
object UidObjectIdGenerator : ObjectIdGenerator {
  override fun cacheKeyForObject(obj: Map<String, Any?>, context: ObjectIdGeneratorContext): CacheKey? {
    val typename = obj["__typename"]?.toString()
    val uid = obj["uid"]?.toString()

    return if (typename != null && uid != null) {
      CacheKey.from(typename, listOf(uid))
    } else {
      null
    }
  }
}

CacheResolver allows resolving a specific field from a query:

query GetProduct($uid: String!) {
  product(uid: $uid) {
    uid
    name
    price
  }
}
object UidCacheResolver: CacheResolver {
  override fun resolveField(field: CompiledField, variables: Executable.Variables, parent: Map<String, Any?>, parentId: String): Any? {
    var type = field.type
    if (type is CompiledNotNullType) {
      type = type.ofType
    }
    if (type !is ObjectType) {
      // This only works for concrete types
      return MapCacheResolver.resolveField(field, variables, parent, parentId)
    }

    val uid = field.resolveArgument("uid", variables)?.toString()
    if (uid != null) {
       return CacheKey.from(type.name, listOf(uid))
    }

    // Always fallback to the default resolver
    return MapCacheResolver.resolveField(field, variables, parent, parentId)
  }
}

Bug fixes

  • Be robust to SDL schemas that already contain builtin definitions (#3241)
Jul 12, 2021

This version is the first alpha on the road to a 3.0.0 release!

Apollo Android 3 rewrites a lot of the internals to be Kotlin first and support a multiplatform cache. It also has performance improvements, new codegen options, support for multiple client directives and much more!

This document lists the most important changes. For more details, consult the preliminary docs and the migration guide.

This is a big release and the code is typically less mature than in the main branch. Please report any issue, we'll fix them urgently. You can also reach out in kotlinlang's #apollo-android slack channel for more general discussions.

Kotlin first

This version is 100% written in Kotlin, generates Kotlin models by default and exposes Kotlin first APIs.

Using Kotlin default parameters, Builders are removed:

import com.apollographql.apollo3.ApolloClient

val apolloClient = ApolloClient("https://com.example/graphql")

Queries are suspend fun, Subscriptions are Flow<Response<D>>:

val response = apolloClient.query(MyQuery())
// do something with response

apolloClient.subscribe(MySubscription()).collect { response ->
    // react to changes in your backend
}

Java codegen is not working but will be added in a future update. For details and updates, see this GitHub issue

Multiplatform

This version unifies apollo-runtime and apollo-runtime-kotlin. You can now use apollo-runtime for both the JVM and iOS/MacOS:

kotlin {
    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("com.apollographql.apollo3:apollo-runtime:3.0.0-alpha01")
            }
        }
    }
}

The normalized cache is multiplatform too and both apollo-normalized-cache (for memory cache) and apollo-normalized-cache-sqlite (for persistent cache) can be added to the commonMain sourceSet.

The In-Memory cache uses a custom LruCache that supports maxSize and weighter options. The SQLite cache uses SQLDelight to support both JVM and native targets.

val apolloClient = ApolloClient("https://com.example/graphql")
                        .withNormalizedCache(MemoryCacheFactory(maxSize = 1024 * 1024))

val apolloRequest = ApolloRequest(GetHeroQuery()).withFetchPolicy(FetchPolicy.CacheFirst)
val apolloResponse = apolloClient.query(apolloRequest)

Current feature matrix:

JVMiOS/MacOSJS
apollo-api (models)
apollo-runtime (network, query batching, apq, ...)🚫
apollo-normalized-cache🚫
apollo-normalized-cache-sqlite🚫
apollo-http-cache🚫🚫

Performance improvements

SQLite batching

SQLite batching batches SQL requests instead of executing them sequentially. This can speed up reading a complex query by a factor 2x+ (benchmarks). This is especially true for queries that contain lists:

{
  "data": {
    "launches": {
      "launches": [
        {
          "id": "0",
          "site": "CCAFS SLC 40"
        },
        ...
        {
          "id": "99",
          "site": "CCBGS 80"
        }
      ]
    }
  }
}

Reading the above data from the cache would take 103 SQL queries with Apollo Android 2 (1 for the root, 1 for data, 1 for launches, 1 for each launch). Apollo Android 3 uses 4 SQL queries, executing all the launches at the same time.

Streaming parser

When it's possible, the parsers create the models as they read bytes from the network. By amortizing parsing during IO, the latency is smaller leading to faster UIs. It's not always possible though. At the moment, the parsers fall back to buffering when fragments are encountered because fragments may contain merged fields that need to rewind in the json stream:

{
    hero {
        friend {
            name
        }
        ... on Droid {
            # friend is a merged field which Apollo Android needs to read twice
            friend {
                id
            }
        }
    }
}

A future version might be more granular and only fallback to buffering when merged fields are actually used.

Codegen options

The codegen has been rethought with the goal of supporting Fragments as Interfaces.

To say it turned out to be a complex feature would be a understatement. GraphQL is a very rich language and supporting all the edge cases that come with polymorphism, @include/@skip directives and more would not only be very difficult to implement but also generate very complex code (see 3144). While fragments as interfaces are not enabled by default, the new codegen allows to experiment with different options and features.

codegenModels

The codegen supports 3 modes:

  • compat (default) uses the same structure as 2.x.
  • operationBased generates simpler models that map the GraphQL operation 1:1.
  • responseBased generates more complex models that map the Sson response 1:1.

responseBased models will generate fragments as interfaces and can always use streaming parsers. They do not support @include/@skip directives on fragments and will generate sensibly more complex code.

Read Codegen.md for more details about the different codegen modes.

To change the codegen mode, add this to your build.gradle[.kts]:

apollo {
    codegenModels.set("responseBased") // or "operationBased", or "compat"
}

Types.kt

The codegen now generates a list of the different types in the schema.

You can use these to get the different __typename in a type-safe way:

val typename = Types.Human.name 
// "Human"

or find the possible types of a given interface:

val possibleTypes = Types.possibleTypes(Types.Character)
// listOf(Types.Human, Types.Droid)

apollo-graphql-ast

This version includes apollo-graphql-ast that can parse GraphQL documents into an AST. Use it to analyze/modify GraphQL files:

file.parseAsGQLDocument()
    .definitions
    .filterIsInstance<GQLOperationDefinition>
    .forEach {
        println("File $file.name contains operation '$it.name'")
    }

Client directives

@nonnull

@nonnull turns nullable GraphQL fields into non-null Kotlin properties. Use them if such a field being null is generally the result of a larger error that you want to catch in a central place (more in docs):

query GetHero {
  # data.hero will be non-null
  hero @nonnull {
    name
  }
}

@optional

Apollo Android distinguishes between:

  • nullable: whether a value is null or not
  • optional: whether a value is present or not

The GraphQL spec makes non-nullable variables optional. A query like this:

query GetHero($id: String) {
    hero(id: $id) {
        name
    }
}

will be generated with an optional id constructor parameter:

class GetHeroQuery(val id: Optional<String?> = Optional.Absent)

While this is correct, this is also cumbersome. If you added the id variable in the first place, there is a very high chance you want to use it. Apollo Android 3 makes variables non-optional by default (but possibly nullable):

class GetHeroQuery(val id: String?)

If for some reason, you need to be able to omit the variable, you can opt-in optional again:

# id will be an optional constructor parameter
query GetHero($id: String @optional) {
    hero(id: $id) {
        name
    }
}

@typePolicy

You can use @typePolicy to tell the runtime how to compute a cache key from object fields:

extend type Book @typePolicy(keyFields: "isbn")

The above will add isbn wherever a Book is queried and use "Book:$isbn" as a cache key.

Since this works at the schema level, you can either modify your source schema or add an extra extra.graphqls file next to it that will be parsed at the same time.

@fieldPolicy

Symmetrically from @typePolicy, you can use @fieldPolicy to tell the runtime how to compute a cache key from a field and query variables.

Given this schema:

type Query {
    book(isbn: String!): Book
}

you can tell the runtime to use the isbn argument as a cache key with:

extend type Query @fieldPolicy(forField: "book", keyArgs: "isbn")

Apollo Adapters

This version includes an apollo-adapters artifact that includes Adapters for common custom scalar types:

  • InstantAdapter for kotlinx.datetime.Instant ISO8601 dates
  • LocalDateAdapter for kotlinx.datetime.LocalDate ISO8601 dates
  • DateAdapter for java.util.Date ISO8601 dates
  • LongAdapter for java.lang.Long
  • BigDecimalAdapter for a MPP com.apollographql.apollo3.BigDecimal class holding big decimal values

WebSockets

This version comes with builtin graphql-ws support in addition to the default graphql-transport-ws. You can use graphql-ws for queries and mutation in addition to subscriptions if your server supports it.

// Creates a client that will use WebSockets and `graphql-ws` for all queries/mutations/subscriptions
val apolloClient = ApolloClient(
    networkTransport = WebSocketNetworkTransport(serverUrl = "wss://com.example/graphql", protocol = GraphQLWsProtocol())
)

Bugfixes

  • Input data class arguments with a default value should be generated as Input.absent() (#3194)
  • Make a high level abstraction for http client (#3119)
  • Add fine grade control on auto persisted query (#3118)
  • Http logging for ApolloHttpNetworkTransport (Kotlin Runtime) (#3107)
  • Multi-modules: simplify the dependency resolution (#3069)
  • KMM - Unable to complete packForXcode to build project containing com.apollographql. apollo3:apollo-normalized-cache-sqlite (#3051)
  • Add typesafe properties for typenames (#3048)
  • Support enums with different cases (#3035)
  • Enable support for the graphql-ws protocol in apollo-android (#3029)
  • Relocate apollo-gradle-plugin dependencies (#3011)
  • Support for modifying the executable document at runtime (#3004)
  • @optional directive for operation variables (#2948)
  • Allow passing null values in Custom JSON Scalars (#2920)
  • multiplatform watchers and store (#2904)
  • Multiplatform optimistic updates (#2903)
  • Multiplatform imperative store API (#2902)
  • change package name and group id for 3.x (#2879)
  • Multiplatform lock-free LRU memory cache (#2844)
  • Multiplatform watchers (#2843)
  • @include on inline Fragments generates non-nullable variable (#2796)
  • Multiplatform FileUpload (#2784)
  • Allow to omit non-nullable variables if they have a defaultValue (#2686)
  • Do not calculate http cache key when the http cache is not used (#2659)
  • Multiplatform normalized store/cache (#2636)
  • Custom scalars in arguments are not correctly serialized to cache keys (#2577)
  • [FileUpload] Remove reflection Component: Kotlin Multiplatform Runtime (#2570)
  • [Plugin] Robustify no-source handling (#2563)
  • [ScalarTypes] Allow tri-state fields (null, undefined, defined) for scalars such as JSON (#2562)
  • CacheKeyResolver.fromFieldArguments should have access to the type information (#2496)
  • move BigDecimal to a custom scalar adapter (#2468)
  • [Compiler] Impossible to query __typename on root objects (#2444)
  • Empty query filter to represent "match all", rather than "match nothing"? (#1961)
  • Allow queries and mutations over websocket (#1197)
Jun 14, 2021

A mini-release to include a fix for query batching (#3146) that didn't make it in time for 2.5.8.

👷‍ Fixes

  • [Query batching] Fix overriding canBeBatched value when cloning QueryCall with toBuilder() (#3146)
Latest
v5.0.0-alpha.7
Tracking Since
Jun 11, 2020
Last checked Apr 20, 2026