Multi Modules codegen
For multi-modules projects, Apollo Kotlin enables you to define queries in a feature module and reuse fragments and types from another module dependency. This helps with better separation of concerns and build times.
Note: this page is for sharing a schema between different modules and defining your
.graphql
operations in different modules. If all your.graphql
files are in a single module, you can useApolloClient
like any other Kotlin dependency without any of this.
Setup
Multi-modules requires that one and only one module contains a schema. This is the schema that all other modules can reuse. In this document, we'll refer to this module as the "schema module".
Configure your schema module to generate Apollo metadata:
1// schema/build.gradle.kts
2apollo {
3 service("service") {
4 generateApolloMetadata.set(true)
5 }
6}
And declare your schema module as a dependency of your feature module:
1// feature/build.gradle.kts
2dependencies {
3 implementation("com.apollographql.apollo3:apollo-runtime:3.8.5")
4 // more regular dependencies
5
6 // Apollo dependencies
7 apolloMetadata(project(":schema"))
8 // You also need to declare the schema module as a regular dependency
9 implementation(project(":schema"))
10}
Resolving Apollo dependencies
A feature module can have any number of apollo module dependencies, each one contributing their types and fragments.
Transitive Apollo dependencies will always expose their fragments and types to the modules downstream. In other words, there is no implementation
vs api
concept like there is for regular dependencies. Apollo dependencies will always expose everything downstream (i.e are treated as api
).
Another important thing to note is that all modules must share the same schema. Place schema.[graphqls | json] in the schema module, the module that is the higher up in the dependencies graph:
1// feature/build.gradle.kts
2// This module must not have a schema
3// This module can use fragments and types from 'shared' and 'schema'
4dependencies {
5 apolloMetadata(project(":shared"))
6}
7
8// shared/build.gradle.kts
9// This module must not have a schema
10// This module can use fragments and types from 'schema'
11dependencies {
12 apolloMetadata(project(":schema"))
13}
14apollo {
15 service("service") {
16 generateApolloMetadata.set(true)
17 }
18}
19
20// schema/build.gradle.kts
21// This module is the schema module
22// Place the schema in this module
23apollo {
24 service("service") {
25 generateApolloMetadata.set(true)
26 }
27}
Multiplatform
For multiplatform projects, put apolloMetadata
in the top level dependencies {}
block:
1// feature/build.gradle.kts
2// This module must not have a schema
3// This module can use fragments and types from 'shared' and 'schema'
4dependencies {
5 apolloMetadata(project(":shared"))
6}
7
8kotlin {
9 jvm()
10
11 sourceSets {
12 val commonMain by getting {
13 dependencies {
14 implementation(project(":shared")
15
16 implementation("com.apollographql.apollo:apollo-api:3.1.0")
17 api("com.apollographql.apollo:apollo-runtime-kotlin:3.1.0")
18 }
19 }
20 }
21}
Summary of different constraints:
All modules must apply the same version of the Apollo Gradle Plugin
All modules using the same schema must use the same service name
The schema module and only the schema module must define schema.[json | graphqls]
The schema module and only the schema module can call
mapScalar
The schema module and only the schema module can define
generateKotlinModels
Type clashes
When using multiple modules, Apollo Kotlin will generate models for every operation and fragment defined in this module. In addition, it's going to generate classes for schema types used by these operation. For an example, for Input Objects, Enums, Custom Scalars, etc...
If two sibling modules use the same schema type and this schema type wasn't generated upstream, each module will generate its own version of the schema type, which could clash. To prevent this, Apollo Kotlin registers a global check${service}ApolloDuplicates
task that will fail if there are duplicates.
If that happens, you will need to resolve the type clash manually by forcing generation of the conflicting type in an upstream module. This is done using the alwaysGenerateTypesMatching
Gradle option:
1// shared/build.gradle.kts
2apollo {
3 service("service") {
4 // For an example if ReviewInput clashes
5 alwaysGenerateTypesMatching.set(listOf("ReviewInput"))
6 // You can also pass Regex patterns
7 alwaysGenerateTypesMatching.set(listOf(".*Input"))
8 }
9}
1// shared/build.gradle
2apollo {
3 service("service") {
4 // For an example if ReviewInput clashes
5 alwaysGenerateTypesMatching.set(["ReviewInput"])
6 // You can also pass Regex patterns
7 alwaysGenerateTypesMatching.set([".*Input"])
8 }
9}