Ktorfit Documentation

repository·master·Indexed 24 days ago

https://github.com/foso/ktorfit

A Kotlin Multiplatform HTTP client library that uses KSP to generate type-safe implementations of API interfaces, similar to Retrofit. It supports Android, iOS, JS, JVM, and Linux, utilizing Ktor clients and a compiler plugin to automate the instantiation of API implementation classes.

Tokens
12.7K
Snippets
43
Records
68
Agent score
85%

What's inside Ktorfit

  1. Overview of Ktorfit

    master
    Ktorfit is an HTTP client and Kotlin Symbol Processor (KSP) designed for Kotlin Multiplatform (KMP) projects. It supports Android, iOS, JS, JVM, and Linux. Inspired by Retrofit, it uses Ktor clients and KSP to generate type-safe API implementations from interfaces.
  2. Understand the Ktorfit project structure

    master

    The Ktorfit repository is organized into several modules. Understanding these helps in locating specific parts of the library or finding examples:

    • compiler plugin: Source code for the compiler plugin.
    • ktorfit-annotations: Module containing the annotations used by Ktorfit.
    • ktorfit-ksp: Source code for the KSP (Kotlin Symbol Processing) plugin.
    • ktorfit-lib-core: Core logic for the Ktorfit library.
    • ktorfit-lib: Combines ktorfit-lib-core with dependencies on platform-specific clients.
    • sandbox: Experimental module for testing new features.
    • example: Contains sample projects demonstrating how to use Ktorfit.
    • docs: Source files for the GitHub documentation page.
  3. Send Multipart data using @Body or @MultiPart

    master

    There are two ways to send multipart data in Ktorfit:

    Option 1: Using @Body

    Pass a parameter of type MultiPartFormDataContent annotated with @Body. You can construct this using Ktor's formData builder.

    Option 2: Using @MultiPart

    Annotate the function with @Multipart. Individual parts are defined using the @Part annotation. All @Part parameters are combined into a single MultiPartFormDataContent request.

    // Option 1: @Body
    interface ExampleService {
        @POST("upload")
        suspend fun upload(@Body map: MultiPartFormDataContent)
    }
    
    // Option 2: @MultiPart
    @Multipart
    @POST("upload")
    suspend fun uploadFile(@Part("description") description: String, @Part("") file: List<PartData>): String
  4. How Ktorfit works under the hood

    master

    Ktorfit operates using three main components that work together during the build process to turn annotated interfaces into functional API clients:

    1. KSP-Plugin: Scans your interfaces for Ktorfit annotations (like @GET) and generates the implementation classes.
    2. Compiler Plugin: A Gradle-integrated plugin that transforms calls to the create() function to inject the generated implementation.
    3. Ktorfit lib: A wrapper around Ktor that provides the runtime infrastructure and simplifies code generation.

    The Generation Lifecycle

    When you define an interface like ExampleApi, KSP generates a class named _ExampleApiImpl in the same package. It also generates a ClassProvider and an extension function createExampleApi() to facilitate instantiation.

    package com.example
    
    import com.example.model.People
    import de.jensklingenberg.ktorfit.http.GET
    
    interface ExampleApi  {
        @GET("/test")
        suspend fun exampleGet(): People
    }
  5. How the Ktorfit compiler plugin works

    master
    The Ktorfit compiler plugin automates the instantiation of API implementation classes. It intercepts calls to the create function from Ktorfit-lib and automatically injects the generated implementation class as an argument. The plugin uses the type parameter provided to the create function to deduce the correct implementation class name (e.g., if you pass ExampleApi, it looks for _ExampleApiImpl).
  6. How the Ktorfit create() function is transformed

    master

    Ktorfit uses a compiler plugin to automate the instantiation of API implementations. When you call the generic create<T>() function, the compiler plugin intercepts this call and replaces it with a call that provides the specific generated implementation class as an argument.

    Original Code:

    val api = jvmKtorfit.create<ExampleApi>()

    Transformed Code (at compile time):

    val api = jvmKtorfit.create<ExampleApi>(_ExampleApiImpl(jvmKtorfit))

    If the compiler plugin is not correctly applied (e.g., the Gradle plugin is missing), the create() function will throw an IllegalArgumentException with the message ENABLE_GRADLE_PLUGIN because it expects the default null value to be replaced by the plugin.

    public fun <T> create(data: T? = null): T {
        if (data == null) {
            throw IllegalArgumentException(ENABLE_GRADLE_PLUGIN)
        }
        return data
    }
  7. How converters work in Ktorfit

    master

    Converters are used to transform HTTP responses or request parameters. To use a converter, you must wrap it in a Converter.Factory and register that factory with the Ktorfit builder using the converterfactories() function.

    There are three main types of converters:

    1. ResponseConverters: For converting HTTP responses.
    2. SuspendResponseConverter: For converting HTTP responses in suspend functions.
    3. RequestParameterConverter: For converting request parameters.
  8. Understand the scope and design goals of Ktorfit

    master

    Ktorfit is designed to provide a developer experience similar to Retrofit for Kotlin Multiplatform (KMP) projects.

    Key architectural decisions include:

    • Ktor-based: Instead of being a 100% drop-in replacement for Retrofit, Ktorfit uses Ktor clients as its engine because Ktor is available on nearly every KMP compile target.
    • Platform Compatibility: Every feature is implemented to ensure compatibility across all platforms supported by Ktor.
    • Ktor Integration: Before adding new functionality, Ktorfit evaluates if an existing Ktor plugin can solve the same problem, ensuring it stays aligned with the Ktor ecosystem.
  9. Understand how `@NoDelegation` affects generated code

    master

    When using @NoDelegation, the Ktorfit compiler plugin changes how the implementation class is structured.

    For a regular interface (without the annotation), Ktorfit generates a delegation clause (e.g., by _SuperTestService1Impl(_ktorfit)). For an interface marked with @NoDelegation, this delegation clause is omitted for that specific interface in the generated class.

    // Example of what is generated for TestService:
    public class _TestServiceImpl(
        private val _ktorfit: Ktorfit,
    ) : TestService, SuperTestService1 by com.example.api._SuperTestService1Impl(_ktorfit) {
        // No delegation for SuperTestService2 because it was marked @NoDelegation
    }
  10. When to use ResponseConverters

    master
    Use ResponseConverter only when you cannot use a suspend function in your interface. Because Ktor relies on Coroutines by default, Ktorfit functions typically require the suspend modifier. For most standard use cases, you should use SuspendResponseConverter instead of a ResponseConverter.
  11. Migrate SuspendResponseConverter to Converter.SuspendResponseConverter

    master

    When implementing custom converters, the SuspendResponseConverter interface has been renamed to Converter.SuspendResponseConverter.

    If you are implementing a Converter.Factory, you should now use the suspendResponseConverter method to return an instance of Converter.SuspendResponseConverter.

    public class CallConverterFactory : Converter.Factory {
    
        override fun suspendResponseConverter(
            typeData: TypeData,
            ktorfit: Ktorfit
        ): Converter.SuspendResponseConverter<HttpResponse, *>? {
            // implementation
        }
    }
  12. Migrate from Call? to 2.0.0+

    master
    In Ktorfit versions 2.0.0 and later, if you use Call? as a return type in your API interfaces, you must explicitly add the ktorfit-converters-call dependency and register the CallConverterFactory in your Ktorfit instance.