Launch Apollo Studio

Multi Modules


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.

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:xyz")
    // 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 can use fragments and types from 'schema'
dependencies {
    apolloMetadata(project(":schema"))
    generateApolloMetadata.set(true)
}

// schema/build.gradle.kts
// This module is the schema module
// Place the schema in this module
dependencies {
    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:x.y.z")
                api("com.apollographql.apollo:apollo-runtime-kotlin:x.y.z")
            }
        }
    }
}

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 must define customScalarsMapping
  • The schema module and only the schema module must 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"))
}
Edit on GitHub