10x in Flutter development with Swagger Codegen
There are many ways to become a 10x developer. For 10x, I do not mean we can be 10 times faster nor 1 developer can finish 10 developers’ work within the same period of time. What I mean is code more efficiently with without sacrificing code quality. In general, a good software architecture can provide us a scalable and extensible code base for a team to work together, which can improve a team’s performance. But today I am going to talk about another approach to perform better — Code Generation.
API calls are common in client-side applications like mobile app and web app. Before implementation, Frontend team and Backend team developers work together to define an API specification based on the user requirement and come up with a API documentation for both parties to work on the integration.
In mobile development, a common way to implement API calls is to manually create a repository class or a server manager class, which defines functions with parameters according to the API documentation, with some networking library like http in Flutter. To consume the response, we need to manually create a model with properties based on the response format and parse the response with some deserialization library like json_serializable. Whenever there is a change in the API specification, we need to manually update related classes to adopt the change. However, because it is a manual process, human mistake cannot be avoided and the Frontend and Backend implement may become unaligned, which is a pain point to developers. To solve the problem, we need automation.
Swagger is an open source tool for developing RESTful APIs, including API specification and code generation for client side.
The API specification can be defined by the OpenAPI specification, formerly Swagger Specification, in YAML or JSON. Its details can be referred to here. This is the part where both Frontend and Backend developers need to work on together before implementation. Here is an example of a user authentication service, which provides endpoints for login and logout, in YAML format.
openapi: 3.0.0
paths:
/authentication/login:
post:
summary: Login with username and password
parameters: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LoginRequest'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/LoginResponse'
'401':
description: Invalid username or password
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Any other exception
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
tags:
- Authentication
/authentication/logout:
post:
summary: Logout user
parameters: []
responses:
'200':
description: OK
'500':
description: Any other exception
content:
application/json:
schema:
type: object
allOf:
- $ref: '#/components/schemas/ErrorResponse'
tags:
- Authentication
security:
- JWT-auth: []
info:
title: User Authentication Service
version: '1.0'
components:
securitySchemes:
JWT-auth:
scheme: bearer
bearerFormat: JWT
type: http
schemas:
LoginRequest:
type: object
properties:
username:
type: string
description: Login user name
example: user1
password:
type: string
description: User's password
example: password
required:
- username
- password
LoginResponse:
type: object
properties:
accessToken:
type: string
description: JWT access token
example: abcdefg
required:
- accessToken
ErrorResponse:
type: object
properties:
errorCode:
type: integer
description: Application error code
required:
- errorCode
We can edit the YAML in Swagger Editor, which visualizes and validates the file.
After finalizing the API specification, both Frontend and Backend developers can start implementation independently until the integration. Today let’s focus on the Frontend part.
SwaggerDartCodeGenerator is a code generator that converts the API specification in JSON format to Dart code in Flutter project.
To get the JSON file, we can manually export and convert the YAML file in the Swagger Editor (File -> Convert and save as JSON). Alternatively, if a public repository is available in GitHub (or any other hosting service), we can upload it to the repository like this for the Swagger generator to download before code generation.
In the Flutter project, we need to import the SwaggerDartCodeGenerator package in pubspec.yaml as follows:
dependencies:
# packages required by the Dart code generated
chopper: 5.2.0
json_annotation: 4.7.0
# any other packages
dev_dependencies:
# packages required by the Swagger Codegen
build_runner: 2.3.3
chopper_generator: 5.2.0
json_serializable: 6.5.4
swagger_dart_code_generator: 2.9.0
Then we need to run pub packages get
(or flutter pub packages get
) to download and install the packages.
After that, we can configure the code generator in build.yaml.
targets:
$default:
builders:
swagger_dart_code_generator:
options:
input_folder: "lib/swaggers/"
output_folder: "lib/generated_code/"
input_urls:
- "https://raw.githubusercontent.com/gogovan/swagger-demo-flutter/main/swagger_demo/swaggers/auth_api.json"
separate_models: true
input_folder
is the folder that stores the Swagger JSON file(s). By default, it works only if it is in the lib
folder. For example, lib/swaggers/
and lib/api_spec
work, but swaggers/
doesn’t! If you want to load from a folder other than lib/
, the path must be included in sources
as follows:
targets:
$default:
sources:
- lib/**
- swagger_examples/**
- swaggers/**
output_folder
is the folder that stores the Dart code generated. Every team member and CICD workflow can generate the same code with the same API spec JSON file (and the same package version), so it can be added to .gitignore
to ignore any file changes.
input_urls
is an optional array that lists the URL of any swagger JSON file. All files will be downloaded to the input_folder
folder for code generation. If the JSON file is stored locally, we can skip it.
separate_models
is a boolean flag that controls if the models are generated into separate file. It is false
by default. My personal preference is to turn it on to make the code generated clearer.
The details on the configuration can be referred to here.
After the configuration, we are all set to generate the API files by running this command:
pub run build_runner build
# or
flutter pub run build_runner build
Sometimes we may need to run the following command prior to the build command.
pub run build_runner clean
# or
flutter pub run build_runner clean
If the Swagger JSON is in correct format, all the files generated will be created/updated at output_folder
according to the configuration.
output_folder
We are now able to call the API endpoints with the Dart code generated. For example, we can simply wrap the functions generated by Swagger in a repository class and then do whatever we want based on the API response.
import 'package:chopper/chopper.dart';
import 'package:swagger_demo/generated_code/auth_api.models.swagger.dart';
import 'package:swagger_demo/generated_code/auth_api.swagger.dart' as swagger;
class AuthApiRepository {
AuthApiRepository(Uri baseUrl)
: _authApi = swagger.AuthApi.create(baseUrl: baseUrl);
final swagger.AuthApi _authApi;
Future<Response?> login({
required String username,
required String password,
String? expiry,
}) =>
_authApi.authenticationLoginPost(
body: LoginRequest(
username: username,
password: password,
),
);
@override
Future<Response?> logout(String? deviceId) =>
_authApi.authenticationLogoutPost();
}
With Swagger Codegen, whenever there is a change in the API spec, we can update our API code with just one line of command, which saves us a lot of time and make the development much more efficient (10x!). Furthermore, we don’t need to worry about any unexpected issue in the API code because the code generator has been well tested and widely used. If there is an error, it is probably because of an unexpected format in the API response. For such case, we can either ask our Backend teammate to fix the response or update the API specification to align with the actual response.
Swagger Codegen is just one example of code generation. In Flutter community, there are other code generators like freezed, which generates model class for cloning and de/serialization, and mockito, which can generates mock class for unit tests.
My demo Flutter project is available at GitHub. Feel free to try the code generator. Thank you for reading this article. Happy coding :)