Streamlining API Integration in Flutter: Auto-Generated Dart Clients with OpenAPI

Streamlining API Integration in Flutter: Auto-Generated Dart Clients with OpenAPI

Introduction

API integration is one of developers' greatest challenges while building mobile applications. While creating user interfaces proves fun and exciting, the complexity arises when it becomes imperative to integrate APIs seamlessly for enhanced functionality and dynamism. This process often leads to extensive collaboration and communication between front-end and back-end engineers. Most times, developers rely on tools such as Swagger or Postman documentation to guide them on API integration, a task that can be perceived as tedious.

This article will explore the OpenAPI Specification and the benefits of using OpenAPI-generated Dart classes in Flutter applications. Also, I'll guide you through specifying APIs using a YAML file, generating the Dart client, and communicating with a backend by just calling regular Dart class methods.

What's OpenApi Specification?

The OpenAPI Specification is a widely adopted standard for describing and documenting RESTFul APIs. It provides a machine-readable interface for APIs, enabling developers to understand the capabilities of an API without having access to its source code. OpenAPI allows developers to define the request parameters, response payloads, authentication methods, HTTP request type, and other relevant details in a well-structured format.

Advantages of using OpenApi?

  1. Eliminate boilerplate code: OpenAPI saves developers significant time and effort, as they don't have to manually write boilerplate code for interacting with APIs.

  2. Consistency: By using a generated client library, developers ensure consistency when interacting with the API across different parts of their application or multiple applications. This consistency helps reduce errors and promotes maintainability.

  3. Up-to-date API Contracts: OpenAPI generators rely on the OpenAPI specification, which serves as a contract for the API. If the API changes, developers can regenerate the client library using the build runner command.

I created a basic spring boot CRUDapplication called note-app. Thisnote-appallows users to create, read, update, and delete notes. There is an instruction on how to run the spring boot app in the README.md file.

Note: The auto-generated Dart classes (dart-client) can communicate with other backend applications written with Ruby, Node, Python, and Go.

NoteApp list of APIs

Below are the note-app endpoints and their sample payload/responses.

  • Get all notes
//Request type: GET
//Endpoint: http://localhost:8080/api/notes
{
    "message": "All notes retrieved successfully",
    "status": "200",
    "data": [
        {
            "id": "969b6dd0-7106-413e-986a-67c0b4a2a24f",
            "title": "Hello world!",
            "description": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
            "createdAt": "2024-05-20T10:00:36.053+00:00",
            "updatedAt": "2024-05-20T10:00:36.053+00:00"
        }
        //...................................
    ]
}
  • Create a single note
//Request type: POST
//Endpoint: http://localhost:8080/api/notes
{
    "title" : "Hello world!",
    "description": "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like)."
}
  • Get a single note by noteId
//Request type: GET
//Endpoint: http://localhost:8080/api/notes/{nodeId}
{
    "message": "Note successfully retrieved",
    "status": "200",
    "data": {
        "id": "f91be527-874e-4d7c-8964-dfc374e2c4bb",
        "title": "Second note",
        "description": "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).",
        "createdAt": "2024-05-20T10:02:53.030+00:00",
        "updatedAt": "2024-05-20T10:02:53.030+00:00"
    }
}
  • Update a single note by noteIdPATCH
//Request type: PATCH
//Endpoint: http://localhost:8080/api/notes/{nodeId}
{
    "title" : "Second note updated",
    "description": "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like)."
}
  • Delete a single note by noteId
//Request type: DELETE
//Endpoint: http://localhost:8080/api/notes/{noteId}

Auto-generating the dart client

Step 1: Create a new directory named noteapp_api_generator

Step 2: Create a new file named noteapi.yaml inside the directory above, and add the code below to the noteapi.yaml file.

openapi: 3.0.0
info: 
  title: NoteApp Api
  version: 1.0.0
  description: ''
tags: 
 - name : Note
servers:
  - url: http://localhost:8080

paths:
  /notes:
    get:
      summary: Retrieve All Notes
      operationId: getAllNotes
      tags:
        - Note
      responses:
        '200': 
          description: Notes successfully retrieved
          content:
            application/json:
              schema: 
                type: object
                $ref: '#/components/schemas/GetNotesResponse'

    post:
      summary: Add new note
      operationId: addNote
      tags:
        - Note
      requestBody:
        description: Add new note
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateNoteModel'
      responses:
        '201':
          description: Note created
          content:
            application/json:
              schema:  
                type: object
                $ref: '#/components/schemas/GetNotesResponse'


  /notes/{noteId}:
    get: 
      summary: Retrieve a single noteId
      operationId: getNoteById
      tags:
        - Note
      parameters:
        - name: noteId
          in: path
          description: Note ID
          required: true
          schema:
            type: string

      responses:
        '200':
          description: Note successfully retrieved
          content:
            application/json:
              schema:
                type: object
                $ref: '#/components/schemas/GetNoteResponse'
...
...
...

To see the full yaml file, check the link

Step 3: Download the openapi-generator-cli.jar file here and place the file in the root of the directory (noteapp_api_generator) you created earlier.

Step 4: Run the command below in your terminal

java -jar openapi-generator-cli-7.2.0.jar generate -i noteapi.yaml -g dart-dio -o ../noteapp_api_client --additional-properties=pubName=noteapp_api_client

After running the command above, a directory called noteapp_api_client will be created with some dart auto-generated classes.

Open the noteapp_api_client directory in your terminal and run the build runner command below. This build runner command will generate all the other needed classes.

flutter pub run build_runner build --delete-conflicting-outputs

Each time you make changes to the specification yaml file, you need to run the two commands in Step 4 above in the same order.

Reference the auto-generated classes in a Flutter app

You can add the noteapp_api_client to your Flutter App in two ways.

1). Manually reference the directory (noteapp_api_client) where you generated the code.

dependencies:
  noteapp_api_client:
      path: ../noteapp_api_client

2). Reference the noteapp_api_client from Github

In a real-world scenario, you can push the files to GitHub , add the GitHub link and reference to your pubspec.yaml file.

dependencies:
  noteapp_api_client:
    git: 
      url: https://github.com/lexican/noteapp_api_client.git
      ref: main

Replace "lexican" in the GitHub link above with your GitHub username

Step 4: Run the flutter pub get command

Start the Spring boot app and ensure it is running on port 8080

Reference the auto-generated classes in your Flutter App

Create an Instance of NoteappApiClient and import the NoteappApiClient

//Import the noteapp_api_client
import 'package:noteapp_api_client/noteapp_api_client.dart';
//Create an instance of NoteappApiClient and configure the Dio
final _noteApi = NoteappApiClient(
  dio: Dio(
    BaseOptions(
      baseUrl: 'http://localhost:8080/api',
    ),
  ),
).getNoteApi();

Get all notes

 Response<GetNotesResponse> response = await _noteApi.getAllNotes();
 final List<NoteModel> notes = response.data?.data?.toList() ?? [];

Add new note

final Response<GetNotesResponse> response = await _noteApi.addNote(
    createNoteModel: CreateNoteModel(
  (b) => b
    ..title = "Sweet Tuesday"
    ..description = "Having a great day",
));
final List<NoteModel> notes = response.data?.data?.toList() ?? [];

Delete Note

final response = await _noteApi.deleteNoteById(noteId: {noteId});

Edit note

final Response<GetNoteResponse> response = await _noteApi.updateNote(
    noteId: {noteId},
    updateNoteModel: UpdateNoteModel(
      (b) => b
        ..title = "Update Note"
        ..description = "Having a great day.",
    ));
final NoteModel note = response.data?.data ?? NoteModel();

Check out the full Flutter code here.

Conclusion

In this blog post, we explored API integration, the OpenAPI Specification, and the benefits of using OpenAPI-generated classes. I also demonstrated how to create a specification yaml file, generate the dart client classes from the terminal/command line, and reference the dart client auto-generated classes from a Flutter app.

In conclusion, integrating APIs effectively is crucial for modern application development, and the OpenAPI Specification offers a powerful framework to streamline this process. Specifically, in Flutter application development, using OpenAPI can greatly speed up the integration of APIs, reduce errors, and enhance the overall application quality.

OpenAPI removes the need to write JSON serializers/deserializers and model classes. Also, with OpenAPI, you don't need to worry about HTTP requests method, request body, or response format and endpoint urls. Embracing OpenAPI Specification is a smart move for developers looking to build robust, scalable, and maintainable applications quickly.

Thank you for reading.

Let's connect on LinkedIn and share great ideas on software development.