title |
---|
Code generation methods in Apollo Kotlin |
Apollo Kotlin provides multiple code generation engines (codegens) that you can choose from depending on your use case:
Codegen | Description |
---|---|
operationBased (default) |
Generates models according to the shape of each defined operation. |
responseBased |
Generates models according to the shape of each operation's response. Compared to |
compat (deprecated) |
Similar to operationBased , except it generates models that are compatible with Apollo Android v2. This codegen helps projects upgrade from v2 to v3. |
💡 For more in-depth information on each codegen, see this design document.
To use a particular codegen, configure codegenModels
in your Gradle scripts:
apollo {
service("service") {
// ...
codegenModels.set("responseBased")
}
}
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 operation's response.
- Polymorphism is handled by generating interfaces. Possible shapes are then defined as different classes that implement the corresponding interfaces.
- Fragments are also generated as interfaces.
- Any merged fields appear once in generated models.
Let's look at examples using fragments to highlight some of these differences.
Consider this query:
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid {
primaryFunction
}
... on Human {
height
}
}
}
If we run the responseBased
codegen on this operation, 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)
}
}
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
Consider this example:
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
fragments:
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 operations using
these fragments):
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)
}
As a convenience, the responseBased
codegen generates methods with the name pattern <fragmentName>
(e.g., droidFields
for a fragment named DroidFields
). This enables you to chain calls together, like so:
val primaryFunction = hero1.droidFields().primaryFunction
val height = hero2.humanFields().height