Releases: apollographql/apollo-kotlin
v3.8.0
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
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
v3.7.4
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
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
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
andhttpHeaders
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
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
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
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
togenerateModelBuilders
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)