Skip to content

Codegen

Maxwell edited this page Jul 24, 2026 · 3 revisions

Code Generation

The retrofit-graphql codegen Gradle plugin (co.anitrend.retrofit.graphql.codegen) parses .graphql operation files at build time and generates Kotlin source code: operation constants, a document registry, enum classes, variable classes, input object classes, typed request helpers, and optional response model data classes.

This is the recommended approach for new projects. It combines the flexibility of hand-written .graphql files with type-safe request construction. Response models are opt-in, operation-scoped, and intentionally narrower than schema-wide generators such as Apollo.

Quick Setup

1. Apply the plugin

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

2. Configure the DSL

retrofitGraphQL {
    packageName.set("co.anitrend.retrofit.graphql.generated")
    schema.set(file("src/main/graphql/schema.graphql"))
    operations.from(fileTree("src/main/graphql") {
        include("**/*.graphql")
    })
    common {
        generateVariables.set(true)
        generateResponses.set(true) // optional, default false
    }
    scalars {
        map("DateTime", "kotlin.String")
        map("GitObjectID", "kotlin.String")
        map("URI", "kotlin.String")
    }
}

3. Add runtime dependencies

dependencies {
    implementation("com.github.AniTrend.retrofit-graphql:runtime:{tag}")
    implementation("com.github.AniTrend.retrofit-graphql:api:{tag}")
    // :annotations and :android-assets are NOT required for codegen-only consumers
    // (they are pulled transitively via :runtime)
}

4. Wire the converter

val factory = GraphConverter.create(
    context = context,
    registry = GeneratedGraphQLRegistry,
)

DSL Options

Option Default Description
packageName (required) Package for generated classes
schema (required) Path to schema.graphql
operations.from (required) File tree of .graphql operation files
common.generateVariables false Generate variable classes, input objects, and request helpers
common.generateResponses false Generate kotlinx-serializable {OperationName}Data response model classes
common.generateHashes true Generate SHA-256 APQ hashes for operations
scalars { map(...) } (none) Map custom GraphQL scalars to Kotlin types

Generated Output

All output goes to build/generated/source/graphql/.

Always generated

Output Description
GeneratedGraphQLRegistry Implements GraphQLDocumentRegistry — provides document text and APQ hashes by operation name
GraphQLOperations Operation name constants
GraphQLDocuments Operation document text constants
GraphQLHashes SHA-256 hash constants (when generateHashes = true)
Enum classes GraphQL enum types generated as Kotlin enum classes

Generated when generateVariables = true

Output Description
Variable classes Data classes implementing GraphQLVariables for each operation's variables
Input object classes Data classes for GraphQL input types referenced by operations
Request helpers .request(...) factory methods on operation objects, returning GraphQLRequest<VariableType>

Generated when generateResponses = true

Output Description
{OperationName}Data Root response model for the operation
Nested object classes Kotlin data classes for selected object fields
Sealed interfaces for abstract types interface/union hierarchies modeled with @JsonClassDiscriminator("__typename")

Response Model Generation

When generateResponses is enabled, the plugin generates kotlinx-serializable models from the operation selection set rather than from the full schema.

Generated response models:

  • Match the exact JSON response shape for the operation
  • Preserve aliases as distinct Kotlin properties
  • Preserve list nullability for both the list container and elements
  • Convert conditional selections (@include / @skip) into nullable Kotlin properties with null defaults when needed
  • Generate sealed interfaces for GraphQL interfaces and unions using __typename discrimination, with no global Json configuration required
retrofitGraphQL {
    common {
        generateResponses.set(true)
    }
    target("anilist") {
        schema.set(file("src/main/graphql/schema.graphql"))
        operations.from(fileTree("src/main/graphql"))
    }
}

Example generated root type:

@Serializable
data class GetCurrentUserData(
    @SerialName("viewer")
    val viewer: Viewer?,
)

You can use the generated model as the GraphContainer payload type in your Retrofit service:

interface GitHubService {
    @POST("/graphql")
    suspend fun getCurrentUser(
        @Body request: GraphQLRequest<Unit>,
    ): Response<GraphContainer<GetCurrentUserData>>
}

Internal architecture

The response-model pipeline uses a normalized intermediate representation:

  • RuntimePath — tracks which concrete runtime types are active at each abstract response path
  • ResponseFieldVariant — keeps field variants separate until projection, instead of collapsing mutually exclusive selections too early
  • Projection phase — selects the active field variant set for one runtime path before Kotlin generation
  • ResponseModelIdentity — exact key used for nested model collection, deduplication, and property type resolution

These internals matter when you rely on nested interfaces, unions, aliased fields, or named fragments. The generated Kotlin types are operation-scoped and branch-aware.

Using Generated Types

Queries without variables

// Generated: GetCurrentUser object with .document and .name properties
val request = GraphQLRequest(
    query = GetCurrentUser.document,
    operationName = GetCurrentUser.name,
)

Queries with variables

// Generated: GetMarketPlaceApps.request(first = 15, after = null)
val request = GetMarketPlaceApps.request(
    first = 15,
    after = "some_cursor",
)
// Returns GraphQLRequest<GetMarketPlaceAppsVariables>

Mutations with variables

// Generated: UploadToStorageBucket.request(upload = "/path/to/file")
val request = UploadToStorageBucket.request(
    upload = filePath,
)
// Returns GraphQLRequest<UploadToStorageBucketVariables>

Retrofit interface

interface GitHubService {
    @POST("/graphql")
    suspend fun getMarketPlaceApps(
        @Body request: GraphQLRequest<GetMarketPlaceAppsVariables>,
    ): Response<GraphContainer<MarketPlaceListings>>
}

Scalar Mappings

Custom GraphQL scalar types used in generated code must be mapped to Kotlin types. Unmapped scalars cause a build error with a path-aware message indicating where the unmapped scalar was encountered.

scalars {
    map("DateTime", "kotlin.String")
    map("GitObjectID", "kotlin.String")
    map("URI", "kotlin.String")
}

Scalars that are mapped but absent from the schema (e.g. "Upload") are allowed — they generate no type but do not fail the build.

Enum Generation

GraphQL enum types are generated as Kotlin enum classes:

enum OrderDirection {
  ASC
  DESC
}

Generates:

enum class OrderDirection {
    ASC,
    DESC,
}

How It Works (Internals)

The codegen pipeline uses these internal components (consumers do not interact with these directly):

  • SchemaIndex — Typed schema metadata index, replacing a flat name set. Enables kind-aware lookups (scalar, enum, input object, etc.).
  • GraphQLTypeMapper — Maps GraphQL types to Kotlin types, branching by schema kind.
  • GraphQLDefaultValueRenderer — Renders default value literals for generated code, replacing duplicated heuristic converters.
  • GraphQLTypeUsageValidator — Recursively validates that all scalar types in generated code have mappings, producing path-aware error messages.
  • ResponseSelectionParser — Normalizes response selections, fragment scopes, aliases, and conditional fields before generation.
  • ResponseModelGenerator — Projects normalized selections into concrete runtime branches and generates {OperationName}Data models.

File Organization

Place .graphql files under src/main/graphql/:

src/main/graphql/
├── schema.graphql
├── queries/
│   ├── GetMarketPlaceApps.graphql
│   ├── GetCurrentUser.graphql
│   └── StorageBucketFiles.graphql
├── mutations/
│   └── UploadToStorageBucket.graphql
└── fragments/
    └── MarketPlaceListingCore.graphql

Further Reading

Clone this wiki locally