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?
Eliminate boilerplate code: OpenAPI saves developers significant time and effort, as they don't have to manually write boilerplate code for interacting with APIs.
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.
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 CRUD
application called note-app. Thisnote-app
allows 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 noteId
PATCH
//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.