Skip to content

Codegen Migration

Maxwell edited this page Aug 2, 2026 · 3 revisions

Switching to Code Generation

This guide covers migrating an existing retrofit-graphql project from the asset-based workflow (@GraphQuery + QueryContainerBuilder + .graphql files in assets/) to the build-time code generation workflow with the backend-neutral request/response contracts.

Note: The asset-based workflow remains supported through the deprecated :compat module. Code generation is an optional enhancement, not a replacement. You can also use both approaches in the same project.

Why Switch?

Asset-Based (legacy :compat) Codegen (backend-neutral)
@GraphQuery annotation on Retrofit methods Generated operation constants and request helpers
QueryContainerBuilder for manual variable construction Typed variable classes with named parameters
Runtime file discovery from assets/ Build-time document registry — no runtime file I/O
String-based variable keys Type-safe variable classes
No enum generation GraphQL enums generated as Kotlin enum classes
GraphContainer<T> / GraphError responses GraphQLResponse<T> / GraphQLData (neutral)
GraphConverter + GraphQLJson (or Gson) GraphQLConverterFactory + explicit GraphQLTransportCodec

Step 1: Apply the Plugin

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

Step 2: Move .graphql Files

Move your .graphql files from assets/graphql/ to src/main/graphql/:

# Before
app/src/main/assets/graphql/queries/GetMarketPlaceApps.graphql

# After
app/src/main/graphql/queries/GetMarketPlaceApps.graphql

Add the schema file:

app/src/main/graphql/schema.graphql

Step 3: Configure the DSL

retrofitGraphQL {
    packageName.set("your.package.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 response model generation
        // serializationBackend is strict and used as-is:
        // NONE stays plain (no auto-selection), KOTLINX emits @Serializable/@SerialName,
        // GSON emits @SerializedName. Default is NONE.
    }
    scalars {
        // Map any custom scalars in your schema
        map("DateTime", "kotlin.String")
        map("GitObjectID", "kotlin.String")
    }
}

Step 4: Update Dependencies

Remove direct :android-assets and :annotations dependencies — they are no longer needed for codegen-only projects. Add an explicit runtime codec:

// Before
dependencies {
    implementation(project(":runtime"))
    implementation(project(":api"))
    implementation(project(":android-assets"))
    implementation(project(":annotations"))
}

// After
dependencies {
    implementation(project(":runtime"))
    implementation(project(":api"))
    implementation(project(":serialization-kotlinx")) // or :serialization-gson
}

:runtime still pulls :android-assets and :annotations transitively, so legacy asset-based fallback remains available if you keep :compat on the classpath.

Step 5: Update Converter Wiring

Replace the legacy GraphConverter construction with the backend-neutral factory plus an explicit codec:

// Legacy (deprecated, :compat) -- only if you keep the GraphConverter/GraphContainer path
val legacyFactory = GraphConverter.create(
    context = context,
    registry = GeneratedGraphQLRegistry,
)

// Backend-neutral (new) -- explicit codec required; no default backend
val factory = GraphQLConverterFactory.create(
    codec = KotlinxGraphQLTransportCodec(),   // or GsonGraphQLTransportCodec()
    registry = GeneratedGraphQLRegistry,
)

Step 6: Update Retrofit Interface

Replace @GraphQuery + QueryContainerBuilder + GraphContainer with the neutral request/response contracts:

// Before (legacy, :compat)
@POST("/graphql")
@GraphQuery("GetMarketPlaceApps")
suspend fun getMarketPlaceApps(
    @Body builder: QueryContainerBuilder,
): Response<GraphContainer<MarketPlaceListings>>

// After (backend-neutral)
@POST("/graphql")
suspend fun getMarketPlaceApps(
    @Body request: GraphQLOperationRequest<GetMarketPlaceAppsVariables>,
): Response<GraphQLResponse<MarketPlaceListings>>

If you enable generateResponses, you can also replace hand-written payload DTOs with generated operation-scoped response models:

suspend fun getMarketPlaceApps(
    @Body request: GraphQLOperationRequest<GetMarketPlaceAppsVariables>,
): Response<GraphQLResponse<GetMarketPlaceAppsData>>

Step 7: Update Call Sites

Replace QueryContainerBuilder with generated request helpers, which return the neutral GraphQLOperationRequest:

// Before (legacy, :compat)
val builder = QueryContainerBuilder()
    .putVariable("first", 15)
    .putVariable("after", cursor)
service.getMarketPlaceApps(builder)

// After (backend-neutral)
val request = GetMarketPlaceApps.request(
    first = 15,
    after = cursor,
)
service.getMarketPlaceApps(request)

Step 8: Handle File Uploads

For multipart uploads, add a RequestBodyPassThroughConverterFactory before the converter. The sample app keeps the bucket upload on the legacy GraphConverter (:compat) because the upload mutation uses the separate bucket schema:

// Legacy (deprecated, :compat) bucket endpoint in the sample app
Retrofit.Builder()
    .addConverterFactory(RequestBodyPassThroughConverterFactory())
    .addConverterFactory(GraphConverter.create(context, registry = GeneratedGraphQLRegistry))
    .baseUrl(baseUrl)
    .build()

Use the generated operation metadata:

val request = UploadToStorageBucket.request(upload = filePath)
// Build MultipartBody from the generated request fields

See GraphQL File Uploads for details.

Gradual Migration

You don't have to migrate everything at once. Both workflows can coexist:

  1. Apply the codegen plugin alongside existing @GraphQuery usage.
  2. Migrate one operation at a time — remove @GraphQuery from a method and switch to GraphQLOperationRequest + GraphQLResponse.
  3. Enable generateResponses only for operations where you want generated payload models.
  4. Once all operations are migrated, remove the legacy :compat dependency, :annotations, and :android-assets direct dependencies.

Further Reading

Clone this wiki locally