Skip to content

Codegen

Maxwell edited this page Aug 2, 2026 · 3 revisions

Code Generation

retrofit-graphql provides an optional Gradle plugin for build-time code generation of typed GraphQL operations. The plugin is applied as:

plugins {
    id("co.anitrend.retrofit.graphql.codegen") version "{latest_version}"
}

Plugin Setup

Repository

pluginManagement {
    repositories {
        maven(url = uri("https://jitpack.io"))
        gradlePluginPortal()
        mavenCentral()
        google()
    }
}

DSL Configuration

The DSL supports both single-target and multi-target modes. Multi-target is recommended for projects that have multiple GraphQL schemas:

retrofitGraphQL {
    common {
        // Shared settings across all targets
        generateOperationConstants.set(true)
        generateDocuments.set(true)
        generateHashes.set(true)
        generateVariables.set(true)
        generateResponses.set(true)
        serializationBackend.set(SerializationBackend.KOTLINX)
    }
    target("github") {
        packageName.set("co.anitrend.retrofit.graphql.sample.generated")
        schema.set(file("src/main/graphql/schema.graphql"))
        operations.from(fileTree("src/main/graphql") {
            include("**/*.graphql")
            exclude("**/bucket/**")
        })
        scalars {
            map("DateTime", "kotlin.String")
            map("GitObjectID", "kotlin.String")
            map("URI", "kotlin.String")
            map("Upload", "kotlin.String")
        }
    }
}

Dependency

Codegen-only consumers need only the converter and the plugin:

dependencies {
    implementation("com.github.AniTrend.retrofit-graphql:runtime:{tag}")
    // :annotations and :android-assets pulled transitively via :runtime
}

For kotlinx.serialization-annotated types (serializationBackend = KOTLINX), add the kotlinx runtime and apply the serialization compiler plugin in your module:

plugins {
    kotlin("plugin.serialization")
}

dependencies {
    implementation("com.github.AniTrend.retrofit-graphql:runtime:{tag}")
    implementation("com.github.AniTrend.retrofit-graphql:serialization-kotlinx:{tag}")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0")
}

kotlinx-serialization is only needed when the generated types (or your runtime codec) use it. The public :api contracts are backend-neutral and require no serializer dependency; serializationBackend = NONE needs no serializer at all beyond your chosen runtime codec.

Generated Output

The plugin generates code into build/generated/source/graphql/{targetName}/:

Generated by default

These outputs are generated by default. Each can be disabled via its corresponding flag in the common {} or target {} DSL blocks (generateOperationConstants, generateDocuments, generateHashes):

Output Type Description
GeneratedGraphQLRegistry GraphQLDocumentRegistry Build-time operation document registry (backend-independent)
GraphQLOperations Constants object Operation name constants
GraphQLDocuments Constants object Operation document string constants (backend-independent; __typename is injected for abstract operations)
GraphQLHashes Constants object SHA-256 hash constants for APQ

generateVariables = true (default false)

Output Type Description
Enum classes Kotlin enums GraphQL enum types from the schema
Variable classes Data classes implementing GraphQLVariables Typed variable classes per operation
Input object classes Data classes GraphQL input types with default values
Request helpers .request(...) factory methods Type-safe GraphQLOperationRequest<VarType> constructors

generateResponses = true (default false)

Output Type Description
Response data classes Per-backend annotated data classes {OperationName}Data with nested types for every selected object field
Sealed interfaces For interfaces/unions __typename-based polymorphism via @JsonClassDiscriminator (KOTLINX) or plain sealed structures (NONE)

Serialization Backend Configuration

The serializationBackend property controls which annotations are emitted on generated types:

retrofitGraphQL {
    common {
        serializationBackend.set(SerializationBackend.KOTLINX)  // SerializationBackend.NONE, .KOTLINX, or .GSON
    }
}

Backend Details

Backend Annotations Response Models R8 Safety
NONE None (strict) Plain data holders; plain sealed interfaces for abstract types N/A (no annotations)
KOTLINX @Serializable, @SerialName, @JsonClassDiscriminator Supported (including interface/union paths) Automatic (kotlinx artifact consumer rules)
GSON @SerializedName Supported (concrete types only) Consumer-owned keep rules may be needed

Strict NONE (no auto-selection)

serializationBackend = NONE is used exactly as configured. There is no auto-selection: setting generateResponses = true with NONE emits plain, unannotated response models (including plain sealed interfaces for abstract types) with no serializer imports, annotations, or adapters:

retrofitGraphQL {
    common {
        generateResponses.set(true)   // stays NONE: plain models, no KOTLINX upgrade
        serializationBackend.set(SerializationBackend.NONE)
    }
}

The generated documents, hashes, and registry are backend-independent: the executable documents still inject __typename for abstract operations, so the wire payload is identical to the KOTLINX/GSON runs. With NONE, abstract-type dispatch on decode is the responsibility of your runtime codec or your own logic, since no discriminator annotations are emitted.

GSON Limitations

GSON + generateResponses = true is only supported for operations whose response types are fully concrete (no interfaces or unions). Gson cannot deserialize polymorphic sealed interfaces needed for GraphQL union and interface response types. Operations with abstract response types will fail at build time with a clear error message. Use KOTLINX for those operations, or use GSON only with generateResponses = false.

Naming Convention

Generated names follow a strict contract:

  • Property names: lowerCamelCase (e.g. createdAt, marketplaceListings)
  • Class names: PascalCase (e.g. GetMarketPlaceAppsData, MarketplaceListingsEdgesNode)
  • Enum constants: SCREAMING_SNAKE_CASE (e.g. OPEN, CREATED_AT)

Keyword Escaping

Kotlin hard keywords and visibility modifiers are escaped by appending Value:

GraphQL Name Kotlin Name @SerialName
private privateValue "private"
object objectValue "object"
when whenValue "when"
is isValue "is"

The original wire name is always preserved in @SerialName, so JSON field names match the GraphQL schema regardless of Kotlin renaming.

Response Model Descriptor Names

Generated response classes use their default fully qualified kotlinx descriptor names. Properties keep GraphQL wire names through property-level @SerialName:

@Serializable
data class GetCurrentUserData(@SerialName("viewer") val viewer: Viewer?) {
    @Serializable
    data class Viewer(@SerialName("login") val login: String)
}

Converter Wiring

The backend-neutral factory requires an explicit runtime codec:

// Koin
single<GraphQLDocumentRegistry> { GeneratedGraphQLRegistry }
single {
    GraphQLConverterFactory.create(
        codec = KotlinxGraphQLTransportCodec(Json { ignoreUnknownKeys = true }),
        registry = get(),
    )
}

The deprecated legacy GraphConverter (:compat) remains available for the asset-based flow:

// Legacy (deprecated, :compat)
single<GraphQLDocumentRegistry> { GeneratedGraphQLRegistry }
single {
    val json = KotlinxGraphQLJson(Json { ignoreUnknownKeys = true })
    GraphConverter.create(
        context = androidContext(),
        json = json,
        registry = get(),
    )
}

Multi-Schema Compose

When you have operations from multiple schemas (e.g. GitHub API + Bucket uploads), compose registries:

internal class CompositeGraphQLRegistry(
    vararg delegates: GraphQLDocumentRegistry,
) : GraphQLDocumentRegistry {
    private val registries: List<GraphQLDocumentRegistry> = delegates.toList()
    override fun document(operationName: String): String? =
        registries.firstNotNullOfOrNull { it.document(operationName) }
    override fun hash(operationName: String): String? =
        registries.firstNotNullOfOrNull { it.hash(operationName) }
}

Registered in Koin:

single<GraphQLDocumentRegistry> {
    CompositeGraphQLRegistry(
        GeneratedGraphQLRegistry,     // codegen output for GitHub schema
        BucketGraphQLRegistry,         // manually maintained for upload schema
    )
}

Further Reading

Clone this wiki locally