Docs
Launch GraphOS Studio

Response based codegen


takes your , generates Kotlin models for them and instantiates them from your JSON responses allowing you to access your data in a type safe way.

There are effectively 3 different domains at play:

  • The domain: s
  • The Kotlin domain: models
  • The JSON domain: responses

By default, generates models that match 1:1 with your . Inline and named generate synthetic , so you can access GraphQL fragments with Kotlin code like data.hero.onDroid.primaryFunction. are classes that can be reused from different . This code generation engine (codegen) is named operationBased because it matches the .

The Json response may have a different shape than your though. This is the case when using merged or . If you want to access your Kotlin properties as they are in the JSON response, provides a responseBased codegen that match 1:1 with the JSON response. are represented as Kotlin interfaces, so you can access their with Kotlin code like (data.hero as Droid).primaryFunction. Because they map to the JSON responses, the responseBased models have the property of allowing JSON streaming and/or mapping to dynamic JS objects. But because is a very expressive language, it's also easy to create a GraphQL query that generate a very large JSON response.

For this reason and other limitations, we recommend using operationBased codegen by default.

This page first recaps how operationBased codegen works before explaining responseBased codegen. Finally, it lists the different limitations coming with responseBased codegen so you can make an informed decision should you use this codegen.

To use a particular codegen, configure codegenModels in your Gradle scripts:

build.gradle.kts
apollo {
service("service") {
// ...
codegenModels.set("responseBased")
}
}

The operationBased codegen (default)

The operationBased codegen generates models following the shape of the .

  • A model is generated for each composite selection.
  • spreads and inline fragments are generated as their own classes.
  • Merged are stored multiple times, once each time they are queried.

For example, given this :

HeroQuery.graphql
query HeroForEpisode($ep: Episode!) {
search {
hero(episode: $ep) {
name
... on Droid {
name
primaryFunction
}
...HumanFields
}
}
}
fragment HumanFields on Human {
height
}

The codegen generates these classes:

HeroQuery.kt
class Search(
val hero: Hero?
)
class Hero(
val name: String,
val onDroid: OnDroid?,
val humanFields: HumanFields?
)
class OnDroid(
val name: String,
val primaryFunction: String
)
HumanFields.kt
class HumanFields(
val height: Double
)

Notice how onDroid and humanFields are nullable in the Hero class. This is because they will be present or not depending on the concrete type of the returned hero:

val hero = data.search?.hero
when {
hero.onDroid != null -> {
// Hero is a Droid
println(hero.onDroid.primaryFunction)
}
hero.humanFields != null -> {
// Hero is a Human
println(hero.humanFields.height)
}
else -> {
// Hero is something else
println(hero.name)
}
}

The responseBased codegen

The responseBased codegen differs from the operationBased codegen in the following ways:

  • Generated models have a 1:1 mapping with the JSON structure received in an 's response.
  • Polymorphism is handled by generating interfaces. Possible shapes are then defined as different classes that implement the corresponding interfaces.
  • are also generated as interfaces.
  • Any merged appear once in generated models.

Let's look at examples using to highlight some of these differences.

Inline fragments

Consider this :

HeroQuery.graphql
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid {
primaryFunction
}
... on Human {
height
}
}
}

If we run the responseBased codegen on this , it generates a Hero interface with three implementing classes:

  • DroidHero
  • HumanHero
  • OtherHero

Because Hero is an interface with different implementations, you can use a when clause to handle each different case:

when (hero) {
is DroidHero -> println(hero.primaryFunction)
is HumanHero -> println(hero.height)
else -> {
// Account for other Hero types (including unknown ones)
// Note: in this example `name` is common to all Hero types
println(hero.name)
}
}

Accessors

As a convenience, the responseBased codegen generates methods with the name pattern as<ShapeName> (e.g., asDroid or asHuman) that enable you to avoid manual casting:

val primaryFunction = hero1.asDroid().primaryFunction
val height = hero2.asHuman().height

Named fragments

Consider this example:

HeroQuery.graphql
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
...DroidFields
...HumanFields
}
}
fragment DroidFields on Droid {
primaryFunction
}
fragment HumanFields on Human {
height
}

The responseBased codegen generates interfaces for the DroidFields and HumanFields :

interface DroidFields {
val primaryFunction: String
}
interface HumanFields {
val height: Double
}

These interfaces are implemented by subclasses of the generated HeroForEpisodeQuery.Data.Hero (and other models for any using these ):

HeroForEpisodeQuery.kt
interface Hero {
val name: String
}
data class DroidHero(
override val name: String,
override val primaryFunction: String
) : Hero, DroidFields
data class HumanHero(
override val name: String,
override val height: Double
) : Hero, HumanFields
data class OtherHero(
override val name: String
) : Hero

This can be used like so:

when (hero) {
is DroidFields -> println(hero.primaryFunction)
is HumanFields -> println(hero.height)
}

Accessors

As a convenience, the responseBased codegen generates methods with the name pattern <fragmentName> (e.g., droidFields for a named DroidFields). This enables you to chain calls together, like so:

val primaryFunction = hero1.droidFields().primaryFunction
val height = hero2.humanFields().height

Limitations of responseBased codegen

  1. Because is a very expressive language, it's easy to create a GraphQL query that generate a very large JSON response. If you're using a lot of nested , the generated code size can grow exponentially with the nesting level. We have seen relatively small queries breaking the JVM limits like maximum method size.
  2. When using , data classes must be generated for each where the fragments are used. To avoid name clashes, the models are nested and this comes with two side effects:
  3. @include, @skip and @defer are not supported on in responseBased codegen. Supporting them would require generating twice the models each time one of these would be used.
Previous
Apollo AST
Edit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc.

Privacy Policy

Company