:robot: Β A strongly-typed, caching GraphQL client for the JVM, Android, and Kotlin multiplatform.
A maintenance release with bugfixes, mostly around WebSockets and subscriptions as well as a LocalTime adapter and options to work with operation manifests.
Huge THANK YOU to @baconz, @AlexHartford, @Oleur for the love they put in WebSocket contributions as well as @Jephuff for their first contribution π .
Service.operationManifestFormat
(#4981)watch()
(#4914)This release is the first alpha of the next major version of Apollo Kotlin: 4.0.0.
This version is under development, but we want to give you a preview of what's coming, and get your early feedback. Please see the roadmap for more details about the release plan.
While version 3 changed a lot of APIs compared to version 2, version 4 should be mostly compatible with version 3. Version 4 will even keep the same package name in order to keep the number of changes low.
The error handling has changed but besides that, the version should be mostly compatible. Please consult the migration guide for all the details.
We would love to hear your feedback on this release. Please report any issues, questions, ideas, or comments on the issue tracker.
See this page for installation instructions and more information.
This plugin for Android Studio and IntelliJ helps you work with Apollo Kotlin. It provides the following features:
ApolloResponse.exception
for error handlingError handling is an important aspect of a client library and we found it could benefit from some changes.
In particular we are moving away from throwing exceptions:
To that effect, there is now an ApolloResponse.exception : ApolloException
property, which will be present when a network error or cache miss have occurred, or when GraphQL errors are present:
val data = response.data
when {
data != null -> {
println("The server returned data: $data")
}
else -> {
// An error happened, check response.exception for more details or just display a generic error
when (response.exception) {
is ApolloGraphQLException -> // do something with exception.errors
is ApolloHttpException -> // do something with exception.statusCode
is ApolloNetworkException -> TODO()
is ApolloParseException -> TODO()
else -> // generic error
}
}
}
To ease the migration to v4, the v3 behavior can be restored by calling ApolloClient.Builder.useV3ExceptionHandling(true)
.
Feedback about this change is welcome on issue 4711.
apollo-runtime-java
for better Java supportAs v3 has a Kotlin and Coroutines first API, using it from Java is sometimes impractical or not idiomatic. That is why in v4 we are introducing a new Java runtime, written in Java, which provides a Java friendly API. It is callback based and doesn't depend on a third-party library like Rx.
To use it in your project, instead of the usual runtime (com.apollographql.apollo3:apollo-runtime
), use the following dependency in your build.gradle[.kts]
file:
implementation("com.apollographql.apollo3:apollo-runtime-java")
Then you can use the ApolloClient
class from Java:
ApolloClient apolloClient = new ApolloClient.Builder()
.serverUrl("https://...")
.build();
apolloClient
.query(MyQuery.builder().build())
.enqueue(new ApolloCallback<MyQuery.Data>() {
@Override public void onResponse(@NotNull ApolloResponse<MyQuery.Data> response) {
System.out.prinitln(response.getData());
}
});
A few examples can be found in the tests.
In multi-module projects, by default, all the types of an upstream module are generated because there is no way to know in advance what types are going to be used by downstream modules. For large projects this can lead to a lot of unused code and an increased build time.
To avoid this, in v3 you could manually specify which types to generate by using alwaysGenerateTypesMatching
. In v4 this can now be computed automatically by detecting which types are used by the downstream modules.
To enable this, add the "opposite" link of dependencies with isADependencyOf()
.
// schema/build.gradle.kts
apollo {
service("service") {
packageName.set("schema")
// Enable generation of metadata for use by downstream modules
generateApolloMetadata.set(true)
// More options...
// Get used types from the downstream module
isADependencyOf(project(":feature1"))
// You can have several downstream modules
isADependencyOf(project(":feature2"))
}
}
// feature1/build.gradle.kts
apollo {
service("service") {
packageName.set("feature1")
// Get the generated schema types (and fragments) from the upstream schema module
dependsOn(project(":schema"))
}
}
This release adds two new artifacts that contain Jetpack compose extensions amongst other fixes.
Many thanks to @slinstacart and @hbmartin for their contributions to this release!
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).
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
}
}
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! π
@JsName
annotation to Operation.name() (#4643)@include
or @skip
with default values (#4700)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!
@skip
and @include
(#4645)This release contains a handful of bug fixes and improvements.
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! π§‘
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.
GraphQLWsProtocol.Factory.webSocketPayloadComposer
(#4589)alwaysGenerateTypesMatching
to execution time (#4578)service {}
in all messages/docs (#4572)This patch release brings a few fixes.
Many thanks to @davidshepherd7, @chao2zhang, @agrosner, @MyDogTom, @doucheng, @sam43 and @vincentjames501, for helping improve the library! π
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")
// ...
+ }
}
canBeBatched
and httpHeaders
orthogonal (#4534)A patch release with a few fixes.
Many thanks to @Holoceo, @juliagarrigos, @davidshepherd7 and @eduardb for the feedbacks π