GraphQL Summit is back for three days of insights, hands-on learning, and fun to celebrate the GraphQL community. Join us in San Diego Oct 3-5.
Docs
Try Apollo Studio

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 use ApolloClient 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:

// schema/build.gradle.kts
apollo {
generateApolloMetadata.set(true)
}

And declare your schema module as a dependency of your feature module:

// feature/build.gradle.kts
dependencies {
implementation("com.apollographql.apollo3:apollo-runtime:3.6.0")
// more regular dependencies
// Apollo dependencies
apolloMetadata(project(":schema"))
// You also need to declare the schema module as a regular dependency
implementation(project(":schema"))
}

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:

// feature/build.gradle.kts
// This module must not have a schema
// This module can use fragments and types from 'shared' and 'schema'
dependencies {
apolloMetadata(project(":shared"))
}
// shared/build.gradle.kts
// This module must not have a schema
// This module can use fragments and types from 'schema'
dependencies {
apolloMetadata(project(":schema"))
}
apollo {
generateApolloMetadata.set(true)
}
// schema/build.gradle.kts
// This module is the schema module
// Place the schema in this module
apollo {
generateApolloMetadata.set(true)
}

Multiplatform

For multiplatform projects, put apolloMetadata in the top level dependencies {} block:

// feature/build.gradle.kts
// This module must not have a schema
// This module can use fragments and types from 'shared' and 'schema'
dependencies {
apolloMetadata(project(":shared"))
}
kotlin {
jvm()
sourceSets {
val commonMain by getting {
dependencies {
implementation(project(":shared")
implementation("com.apollographql.apollo:apollo-api:3.1.0")
api("com.apollographql.apollo:apollo-runtime-kotlin:3.1.0")
}
}
}
}

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:

// shared/build.gradle.kts
apollo {
// For an example if ReviewInput clashes
alwaysGenerateTypesMatching.set(listOf("ReviewInput"))
// You can also pass Regex patterns
alwaysGenerateTypesMatching.set(listOf(".*Input"))
}
// shared/build.gradle
apollo {
// For an example if ReviewInput clashes
alwaysGenerateTypesMatching.set(["ReviewInput"])
// You can also pass Regex patterns
alwaysGenerateTypesMatching.set([".*Input"])
}
Edit on GitHub
Previous
Codegen methods
Next
Connecting source sets