Easily integrate an unknown API into your Android app with Postman and OpenAPI

How to generate DTOs for your app, when all you have in your tool belt is a raw HTTP response ?

ยท

4 min read

The problem

Swagger and OpenAPI have been here for a while now, and it has greatly improved the way backend teams expose, maintain, and, above all, communicate on their APIs.

As a frontend developer, I've worked on projects where backend people where just at the other side of the open space. So, each time a change in the API was available, the backend folks gently told us to regenerate the API interfaces based on a new version of the OpenAPI's definition file (yaml format).

This is standard procedure.

Unfortunately, on some of my personal projects, based on public APIs, I'm often unable to get my hands on the OpenAPI definitions. The only option left is to dive deep into the API's documentation, analyse HTTP responses in Postman and manually add DTOs to the data provider layer of my application. A bit frustrating, I admit...

After some research, I came across this tool. Basically it converts a Postman collection into an OpenAPI definition file.

That's a start, but how can we use it to integrate an arbitrary HTTP response into an application ?

Let's see this in practice.

๐Ÿ’ก
I take as an example an Android app I'm currently working on. It's a weather app, which relies on the OpenWeather API. It's a great API, whose free plan is perfectly fine for my needs.

Postman collection to the rescue

As first step, I create a Postman collection containing a GET request and associate an example to it. Adding an example is mandatory if you want to properly generate the OpenAPI schema with the postman2openapi tool.

In the context of my Android app, I'm relying on the "OneCall" endpoint of the OpenWeather API :

The associated example is a JSON dump of Paris' weather data (short-term and long-term forecasts) :

After exporting the Postman collection, I simply copy/paste it into the postman2openapi tool, which instantly produces the OpenAPI definition file ๐Ÿ˜€.

Integration into a Kotlin Android app

I then put those files into a specific folder, open_weather_api, at the root of my Gradle subproject :

The OpenWeather.yaml file is paste "as is", except for the tags field that I add explicitly

servers:
  - url: https://api.openweathermap.org
paths:
  /data/2.5/onecall:
    get:
      summary: one call
      description: one call
      operationId: oneCall
      tags: # add "tags" manually
        - weather 
      parameters:

This simple trick allows to override the name of the generated API interface name, for me WeatherApi.kt (instead would be DefaultApi.kt).

The template/ directory contains OpenAPI's mustache templates I've generated and modified for my specific needs. For example, I've marked all the Kotlin data class (which are the programmatic representation of DTOs) as internal.

OpenAPI Gradle configuration

In an Android project, the OpenAPI generator can be integrated via the Gradle plugin org.openapi.generator, giving access to tasks and extending Gradle DSL. The following is the configuration I use for my project, you may find it helpful :

openApiGenerate {
    generatorName = "kotlin"
    outputDir = "$projectDir"
    templateDir = "$projectDir/open_weather_api/template"
    inputSpecRootDirectory = "$projectDir/open_weather_api"
    modelPackage = "com.tibo47.weatherPaname.weather.dataprovider.remote.dto"
    apiPackage = "com.tibo47.weatherPaname.weather.dataprovider.remote.api"
    modelNameSuffix = "Dto"
    // for details :  https://openapi-generator.tech/docs/globals/#available-global-properties
    globalProperties = mapOf(
        "apis" to "",
        "models" to "",
        "modelDocs" to "false",
        "apiDocs" to "false",
    )
    additionalProperties = mapOf(
        "sourceFolder" to "src/main/java",
        "useCoroutines" to "true",
        "library" to "jvm-retrofit2",
        "serializationLibrary" to "kotlinx_serialization",
    )
}

I strongly advice you to have a look at the globalProperties documentation, which is a gold mine of information.

DTOs and API generation ๐Ÿš€

The plugin exposes the task :

./gradlew openApiGenerate

This will generate the appropriate DTOs and the API interface file, configured to be a Retrofit implementation :

internal interface WeatherApi {
    /**
     * one call
     * one call
     * Responses:
     *  - 200: one call
     *
     * @param lat  (optional)
     * @param lon  (optional)
     * @param appid  (optional)
     * @param units  (optional)
     * @return [OneCall200ResponseDto]
     */
    @GET("data/2.5/onecall")
    suspend fun oneCall(
        @Query("lat") lat: kotlin.String? = null,
        @Query("lon") lon: kotlin.String? = null,
        @Query("appid") appid: kotlin.String? = null,
        @Query("units") units: kotlin.String? = null,
    ): Response<OneCall200ResponseDto>
}

Due to the modelPackage and apiPackage options, files will be written in the appropriate packages :

Those files must be checked into Git and should be regenerated if necessary, when a change in the OpenWeather API happens (e.g on a version upgrade).

If you have a linter (I hope you have) you will encounter lots of formatting errors. You can either try to fix them by editing the OpenAPI's mustache templates (tedious) or ignore them altogether. I stick to the second solution, by configuring the kotlinter-gradle plugin I use in my stack :

val fileTree = fileTree("src/main")
fileTree.include("**/*Dto.kt")
fileTree.include("**/WeatherApi.kt")

tasks.withType<LintTask> {
    this.source = this.source.minus(fileTree).asFileTree
}

tasks.withType<FormatTask> {
    this.source = this.source.minus(fileTree).asFileTree
}

I hope this article will help you to quickly and securely generate and use public APIs for your apps โœŒ๏ธ.

If you have feedbacks or questions, don't hesitate to hit the Comments button, floating around. I'm also on Mastodon for Android, under the id 47tibo if you prefer.

ย