Skip to content

Releases: apollographql/apollo-kotlin

v3.8.0

03 Apr 11:51
Compare
Choose a tag to compare

This release adds two new artifacts that contain Jetpack compose extensions amongst other fixes.

💙️ External contributors

Many thanks to @slinstacart and @hbmartin for their contributions to this release!

✨ [New] Jetpack compose extension (#4802)

You can now use the apollo-compose-support artifact:

// build.gradle.kts
dependencies {
  implementation("com.apollographql.apollo3:apollo-compose-support")
}

This artifact contains the toState() and watchAsState() extensions:

/**
 * A stateful composable that retrieves your data
 */
@OptIn(ApolloExperimental::class)
@Composable
fun LaunchDetails(launchId: String) {
    val response by apolloClient.query(LaunchDetailsQuery(launchId)).toState()
    val r = response
    when {
        r == null -> Loading() // no response yet
        r.exception != null -> ErrorMessage("Oh no... A network error happened: ${r.exception!!.message}")
        r.hasErrors() -> ErrorMessage("Oh no... A GraphQL error happened ${r.errors[0].message}.")
        else -> LaunchDetails(r.data!!, navigateToLogin)
    }
}

/**
 * A stateless composable that displays your data
 */
@Composable
private fun LaunchDetails(
        data: LaunchDetailsQuery.Data,
) {
  // Your UI code goes here
}

If you are working with paginated data, you can also add apollo-compose-paging-support to your dependencies:

// build.gradle.kts
dependencies {
  implementation("com.apollographql.apollo3:apollo-compose-paging-support")
}

This artifact contains a helper function to create androidx.pagin.Pager instances (androix documentation):

@OptIn(ApolloExperimental::class)
@Composable
fun LaunchList(onLaunchClick: (launchId: String) -> Unit) {
  val lazyPagingItems = rememberAndCollectPager<LaunchListQuery.Data, LaunchListQuery.Launch>(
          config = PagingConfig(pageSize = 10),
          appendCall = { response, loadSize ->
            if (response?.data?.launches?.hasMore == false) {
              // No more pages
              null
            } else {
              // Compute the next call from the current response
              apolloClient.query(
                      LaunchListQuery(
                              cursor = Optional.present(response?.data?.launches?.cursor),
                              pageSize = Optional.present(loadSize)
                      )
              )
            }
          },
          getItems = { response ->
            // Compute the items to be added to the page from the current response
            if (response.hasErrors()) {
              Result.failure(ApolloException(response.errors!![0].message))
            } else {
              Result.success(response.data!!.launches.launches.filterNotNull())
            }
          },
  )
  
  // Use your paging items:
  if (lazyPagingItems.loadState.refresh is LoadState.Loading) {
    Loading()
  } else {
    LazyColumn {
      items(lazyPagingItems) { launch ->
        // Your UI code goes here
      }
      item {
        when (val append = lazyPagingItems.loadState.append) {
          is LoadState.Error -> // Add error indicator here 
          LoadState.Loading -> // Add loading indicator here
        }
      }
    }
  }
}

As always, feedback is very welcome. Let us know what you think of the feature 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).

✨ [New] Gradle plugin: run codegen after gradle sync

If you import a new project or run a Gradle sync, your GraphQL models are now automatically generated so that the IDE can find the symbols and your files do not show red underlines. This takes into account Gradle up-to-date checks and it should be pretty fast. If you want to opt-out, you can do so with generateSourcesDuringGradleSync.set(false):

apollo {
  // Enable automatic generation of models during Gradle sync (default)
  generateSourcesDuringGradleSync.set(true)

  // Or disable automatic generation of models to save on your Gradle sync times
  generateSourcesDuringGradleSync.set(false)

  service("api") {
    // Your  GraphQL configuration
  }
}

👷‍ All changes

  • Allow to add HTTP headers on top of ApolloClient ones (#4754)
  • Kotlin 1.8 (#4776)
  • Move cache creation outside the main thread (#4781)
  • Cache: ignore hardcoded @include(if: false) directives (#4795)
  • Add % to reserved characters to encode in URL (#4804)
  • First drop of experimental Compose support libraries (#4783)
  • Consider variable default values with @skip/@include/@defer (#4785)
  • Gradle plugin: run codegen after gradle sync (#4796)
  • Allow custom SqlDriver (#4806)
  • Multipart subscriptions (#4768, #4807, #4738)
  • GQLNode.print for type extensions (#4814)

v3.7.5

14 Mar 20:57
@BoD BoD
Compare
Choose a tag to compare

This release contains a bunch of fixes and minor improvements.

Many thanks to @adubovkin and @ndwhelan for contributing to the project, and to all the people who sent feedback! 💜

🐛 Bug fixes

  • Fix CacheFirst + emitCacheMisses(true) (#4708)
  • Fix content-length for the String constructor in MockServer (#4683)
  • Fix same shape validation (#4641)
  • Add a @JsName annotation to Operation.name() (#4643)
  • Remove unreachable lenient mode from JSON writer/reader (#4656)
  • Remove deprecation on connectToAndroidSourceSet because alternative have issues too (#4674)
  • Fail fast if trying to set browser WebSocket headers (#4676)
  • Make sure the fallback type is always last (#4692)
  • Fix normalizing data when using @include or @skip with default values (#4700)
  • Java codegen: fix h nameclash in hashCode (#4715)

🔍 Deprecation warnings (#4610)

As we're starting to work on version 4.0 which will drop support for the "compat" codegen and a few other options dating from version 2, we've added in this release some deprecation warnings that will warn when they're used. If you haven't done already, now is a good time to migrate!

👷‍ Other changes

  • Data builders: support for @skip and @include (#4645)
  • SchemaDownloader: Update to download deprecated input fields (#4678)
  • Include deprecated arguments and directives in introspection (#4702)
  • Update JS dependencies (#4634)

v3.7.4

13 Jan 10:47
Compare
Choose a tag to compare

This release contains a handful of bug fixes and improvements.

👷‍ All changes

  • Kotlin codegen: automatically escape 'companion' fields (#4630)
  • Runtime: fix a case where APQ + cache could be misconfigured and throw an exception (#4628)
  • Update KTOR to 2.2.2 (#4627)
  • Allow having an empty last part in multipart (#4598)
  • Add data builders for unknown interface and union types (#4613)
  • Http cache: don't access the disk from the main thread in error case (#4606)

v3.7.3

20 Dec 11:34
@BoD BoD
Compare
Choose a tag to compare

This release contains a handful of bug fixes and improvements, and also discontinues the legacy JS artifacts.

Many thanks to @StefanChmielewski and @chao2zhang for contributing to the project! 🧡

⚙️ Removed JS legacy artifacts (#4591)

Historically, Kotlin Multiplatform has had 2 formats of JS artifacts: Legacy and IR, and Apollo Kotlin has been publishing both. However, the Legacy format is about to be deprecated with Kotlin 1.8 and moreover we've seen issues when using the Legacy artifact in the browser. That is why starting with this release, only the IR artifacts will be published. Please reach out if this causes any issue in your project.

👷‍ All changes

  • Add GraphQLWsProtocol.Factory.webSocketPayloadComposer (#4589)
  • Escape "Companion" in enum value names (#4558)
  • Un-break Gradle configuration cache in multi module cases (#4564)
  • Move computing the alwaysGenerateTypesMatching to execution time (#4578)
  • Log deprecation warning instead of printing (#4561)
  • Escape spaces when url encoding, for Apple (#4567)
  • Fix providing linker flags to the Kotlin compiler with KGP 1.8 (#4573)
  • Use service {} in all messages/docs (#4572)
  • Print all duplicate types at once (#4571)
  • Fix JavaPoet formatting (#4584)
  • Don't publish legacy js artifacts (#4591)

v3.7.2

05 Dec 16:15
@BoD BoD
Compare
Choose a tag to compare

This patch release brings a few fixes.

Many thanks to @davidshepherd7, @chao2zhang, @agrosner, @MyDogTom, @doucheng, @sam43 and @vincentjames501, for helping improve the library! 🙏

🔎‍ Explicit service declaration

Apollo Kotlin can be configured to work with multiple services and have the package name, schema files location, and other options specified for each of them. When using a single service however it is possible to omit the service block and set the options directly in the apollo block - in that case, a default service named service is automatically defined.

While this saves a few lines, it relies on Gradle afterEvaluate {} block that makes the execution of the plugin less predictable and more subject to race conditions with other plugins (see here for an example).

What's more, as we move more logic to build time, the name of the service is going to be used more and more in generated code. Since explicit is better than implicit, mandating that service name sounds a good thing to do and a warning is now printed if you do not define your service name.

To remove the warning, embed the options into a service block:

apollo {
+ service("service") {
    packageName.set("com.example")
    // ...
+ }
}

👷‍ All changes

  • Improve "duplicate type" message by using the full path of the module (#4527)
  • Fix using apollo2 and apollo3 Gradle plugins at the same time (#4528)
  • Add a warning when using the default service (#4532)
  • Fix Java codegen in synthetic fields when using optionals (#4533)
  • Make canBeBatched and httpHeaders orthogonal (#4534)
  • Fix item wrongly removed from http cache when error in subscriptions (#4537)
  • Do not throw on graphql-ws errors and instead return the errors in ApolloResponse (#4540)
  • graphql-ws: send pong while waiting for connection_ack (#4555)

v3.7.1

18 Nov 11:27
Compare
Choose a tag to compare

A patch release with a few fixes.

👷‍ All changes

  • 👷Data Builders: make DefaultFakeResolver open and stateless (#4468)
  • Kotlin 1.7.21 (#4511)
  • Introduce HttpFetchPolicyContext (#4509)
  • Fix usedCoordinates on interfaces (#4506)

Many thanks to @Holoceo, @juliagarrigos, @davidshepherd7 and @eduardb for the feedbacks 💙

v2.5.14

18 Nov 11:26
Compare
Choose a tag to compare

A patch release to fix an issue where the ApolloCall could end up in a bad state. Many thanks to @WilliamsDHI for diving into this 💙!

👷‍ All changes

  • update terminate and responseCallback methods to return Optional.absent() in IDLE/TERMINATED state (#4383)

v3.7.0

08 Nov 17:31
Compare
Choose a tag to compare

This version adds multiple new low level features. These new features expose a lot of API surface, and they will probably stay experimental until 4.0. Feedback is always very welcome.

✨️ [new & experimental] compiler hooks API (#4474, #4026)

Compiler hooks allow you to tweak the generated models by exposing the underlying JavaPoet/KotlinPoet structures. You can use it for an example to:

  • Add a 'null' default value to model arguments (source)
  • Introduce a common interface for all models that implement __typename (source)
  • Add a prefix to generated models (source)
  • Any other thing you can think of

To do so, make sure to use the "external" version of the plugin:

plugins {
  // Note: using the external plugin here to be able to reference KotlinPoet classes
  id("com.apollographql.apollo3.external")
}

And then register your hook to the plugin:

apollo {
  service("defaultnullvalues") {
    packageName.set("hooks.defaultnullvalues")
    compilerKotlinHooks.set(listOf(DefaultNullValuesHooks()))
  }
}

✨️ [new & experimental] operationBasedWithInterfaces codegen (#4370)

By default, Apollo Kotlin models fragments with synthetic nullable fields. If you have a lot of fragments, checking these fields requires using if statements. For an example, with a query like so:

{
  animal {
    species
    ... on WarmBlooded {
      temperature
    }
    ... on Pet {
      name
    }
    ... on Cat {
      mustaches
    }
  }
}

you can access data like so:

if (animal.onWarmBlooded != null) {
  // Cannot smart cast because of https://youtrack.jetbrains.com/issue/KT-8819/
  println(animal.onWarmBlooded!!.temperature)
}
if (animal.onPet != null) {
  println(animal.onPet!!.name) 
}
if (animal.onCat != null) {
  println(animal.onCat!!.mustaches)
}

Some of the combinations could be impossible. Maybe all the pets in your schema are warm blooded. Or maybe only cat is a warm blooded. To model this better and work around KT-8819, @chalermpong implemented a new codegen that adds a base sealed interface. Different implementations contain the same synthetic fragment fields as in the default codegen except that their nullability will be updated depending the branch:

when (animal) {
  is WarmBloodedPetAnimal -> {
    println(animal.onWarmBlooded!!.temperature)
    println(animal.onPet!!.name)
  }
  is PetAnimal -> {
    // Some pet that is not warm blooded, e.g. a Turtle maybe?
    println(animal.onPet!!.name)
  }
  is OtherAnimal -> {
    println(animal.species)
  }
  // Note how there is no branch for Cat because it's a WarmBloodedPetAnimal
  // Also no branch for WarmBlooded animal because all pets in this (fictional) sample schema are WarmBlooded. This could be different in another schema
}

To try it out, add this to your Gradle scripts:

apollo {
  codegenModels.set("experimental_operationBasedWithInterfaces") 
}

Many many thanks to @chalermpong for diving into this 💙

✨️ [new & experimental] usedCoordinates auto detection (#4494)

By default, Apollo Kotlin only generates the types that are used in your queries. This is important because some schemas are really big and generating all the types would waste a lot of CPU cycles. In multi-modules scenarios, the codegen only knows about types that are used locally in that module. If two sibling modules use the same type and that type is not used upstream, that could lead to errors like this:

duplicate Type '$Foo' generated in modules: feature1, feature2
Use 'alwaysGenerateTypesMatching' in a parent module to generate the type only once

This version introduces new options to detect the used types automatically. It does so by doing a first pass at the GraphQL queries to determine the used type. Upstream modules can use the results of that computation without creating a circular dependency. To set up auto detection of used coordinates, configure your schema module to get the used coordinates from the feature module using the apolloUsedCoordinates configuration:

// schema/build.gradle.kts
dependencies {
  implementation("com.apollographql.apollo3:apollo-runtime")
  // Get the used coordinates from your feature module
  apolloUsedCoordinates(project(":feature"))
  // If you have several, add several dependencies
  apolloUsedCoordinates(project(":feature-2"))
}
apollo {
  service("my-api") {
    packageName.set("com.example.schema")
    generateApolloMetadata.set(true)
  }
}

And in each of your feature module, configure the apolloSchema dependency:

// feature/build.gradle.kts
dependencies {
  implementation("com.apollographql.apollo3:apollo-runtime")
  // Depend on the codegen from the schema
  apolloMetadata(project(":schema"))
  // But also from the schema so as not to create a circular dependency
  apolloSchema(project(":schema"))
}
apollo {
  // The service names must match
  service("my-api") {
    packageName.set("com.example.feature")
  }
}

👷‍ All changes

  • Add usedCoordinates configuration and use it to automatically compute the used coordinates (#4494)
  • Compiler hooks (#4474)
  • 🐘 Use registerJavaGeneratingTask, fixes lint trying to scan generated sources (#4486)
  • Rename generateModelBuilder to generateModelBuilders and add test (#4476)
  • Data builders: only generate used fields (#4472)
  • Only generate used types when generateSchema is true (#4471)
  • Suppress deprecation warnings, and opt-in in generated code (#4470)
  • Multi-module: fail if inconsistent generateDataBuilders parameters (#4462)
  • Add a decapitalizeFields option (#4454)
  • Pass protocols to WebSocket constructor in JSWebSocketEngine (#4445)
  • SQLNormalized cache: implement selectAll, fixes calling dump() (#4437)
  • Java codegen: Nullability annotations on generics (#4419)
  • Java codegen: nullability annotations (#4415)
  • OperationBasedWithInterfaces (#4370)
  • Connect test sourceSet only when testBuilders are enabled (#4412)
  • Java codegen: add support for Optional or nullable fields (#4411)
  • Add generatePrimitiveTypes option to Java codegen (#4407)
  • Add classesForEnumsMatching codegen option to generate enums as Java enums (#4404)
  • Fix data builders + multi module (#4402)
  • Relocate the plugin without obfuscating it (#4376)

v3.6.2

05 Oct 14:11
Compare
Choose a tag to compare

A patch version to fix compatibility with Kotlin 1.7.20 and another fix when calling ApolloStore.dump() with the SQL normalized cache.

👷‍ All changes

  • Add support for Kotlin Gradle Plugin 1.7.20 (#4439)
  • Fix SQLNormalizedCache.dump() (#4437)

v3.6.1

03 Oct 15:50
Compare
Choose a tag to compare

A patch version to fix an issue with data builder and multi modules. Many thanks to @agrosner and @eduardb for catching this.

👷‍ All changes

  • Fix data builders in multi-modules scenarios (#4402)