Generated Code in Flutter ”Retrofit”
Uploading Images to an API Using Retrofit in Flutter

Peace upon you,
In this article, we’ve covered how to set up and use the Retrofit package in Flutter to upload an image to an API. With Retrofit’s type-safe HTTP client and Dio’s efficient networking capabilities, uploading images becomes simple and maintainable. The process involves adding dependencies, generating code, and writing the necessary logic to handle image selection and upload.
retrofit.dart is a type conversion dioclient generator using source_gen and inspired by Chopperand Retrofit.
What is Generated Code in Flutter?
In Flutter (and Dart), generated code refers to code that is automatically created by a tool or package based on predefined rules, templates, or annotations. Instead of manually writing all the boilerplate code yourself, certain tools (like build_runner, json_serializable, and retrofit_generator) can generate large portions of your code based on the information you provide through annotations or configuration files.
Generated code can include:
- API clients (like with the
retrofitpackage). - Data models (like with the
json_serializablepackage). - Dependency injection setup.
- Code for state management.
These tools help automate repetitive tasks, allowing developers to focus more on core business logic.
How Generated Code Works in Flutter
In Flutter, tools like build_runner are often used to generate code. For instance, if you use a package like json_serializable or retrofit, you will annotate your classes or methods, and when you run the code generation command, it will create a file (often ending in .g.dart) that contains all the code needed to implement those annotations.
Here’s a simple process for generated code:
- Annotation: You annotate your Dart code with special annotations provided by the package you’re using.
- Code Generation: You run a command like
flutter pub run build_runner buildto generate the necessary code. - Generated Code: A new file is generated (e.g.,
api_client.g.dart), which contains the implementation based on the annotations and configuration you provided.
Benefits of Generated Code in Flutter
- Reduction of Boilerplate Code: Generated code helps eliminate the need to write repetitive code, especially for common tasks like network requests, data parsing, and state management.
- Improved Productivity: Since generated code automates routine tasks, developers can spend more time focusing on business logic rather than implementing low-level details like serializing JSON or setting up HTTP clients.
- Consistency: Generated code ensures a consistent structure across your application. This can help in avoiding human errors and maintaining uniformity in your codebase.
- Simplified Maintenance: When your app evolves, and you need to update parts of your code (e.g., adding new API endpoints or modifying models), it’s easy to make changes in your annotations, re-run the generator, and have the required code updated automatically.
- Type Safety: By using generated code with Dart’s strong type system, you can avoid runtime errors and improve the reliability of your code. For example, if the generated code doesn’t match the model or method signatures, you’ll get compile-time errors, helping catch bugs early.
- Separation of Concerns: Code generation allows developers to separate the concern of data modeling and networking logic. Instead of manually handling these concerns in your code, you describe them, and the generator does the rest.
Let do our Example …
Step 1: Add Dependencies
Add the following dependencies to your pubspec.yaml file:
dependencies:
dio: ^5.0.10
retrofit: ^4.4.1
json_annotation: ^4.7.0
logger: ^1.1.0 # Optional for debugging and logging requests
dev_dependencies:
retrofit_generator: ^4.4.1
build_runner: ^2.1.7
Run the following command to install the packages:
flutter pub get
Step 2: Define the API Client
We will define the API client using Retrofit annotations. Create a new file named api_client.dart and define the interface:
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:retrofit/retrofit.dart';
part 'api_client.g.dart';
@RestApi(baseUrl: "https://yourapi.com/")
abstract class ApiClient {
factory ApiClient(Dio dio, {String baseUrl}) = _ApiClient;
@POST("/upload")
@MultiPart()
Future<void> uploadImage(
@Part(name: "file") File file,
@Part(name: "description") String description,
);
}
In this code:
@MultiPart()is used to indicate that this is a multipart request.@Part()specifies the parts of the request, such as the image file and any other data (e.g., description).
Step 3: Generate API Client Code
Once you define the API client, use build_runner to generate the code that Retrofit uses to interact with the API. Run the following command in your terminal:
flutter pub run build_runner build
Step 4: Initialize Dio and ApiClient
You need to create a Dio instance and initialize the ApiClient to make API calls. You can do this in your main.dart or in a separate service file.
import 'package:dio/dio.dart';
import 'api_client.dart';
void main() {
final dio = Dio(); // Dio instance with default options
final apiClient = ApiClient(dio);
runApp(MyApp());
}
Step 5: Upload Image to API
To upload an image, you will typically get the image from the gallery or camera using the image_picker package. Here’s an example function that handles image selection and uploading:
import 'dart:io';
import 'package:image_picker/image_picker.dart';
import 'api_client.dart';
void uploadImageToApi(ApiClient apiClient) async {
final picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
File file = File(image.path);
try {
await apiClient.uploadImage(file, "Sample Image Description");
print("Image uploaded successfully!");
} catch (e) {
print("Error uploading image: $e");
}
} else {
print("No image selected");
}
}
In this example:
- We use the
ImagePickerto allow the user to select an image from their gallery. - The selected image is converted to a
Fileand uploaded to the API using theuploadImage()method. - The
descriptionis sent along with the file, but it can be replaced or removed if not neede
Step 6: Handle API Response
You can handle responses or errors more effectively by wrapping the upload call in a try-catch block and handling different status codes from the API:
void uploadImageToApi(ApiClient apiClient) async {
try {
final picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
File file = File(image.path);
// Attempt to upload the image
await apiClient.uploadImage(file, "Image Upload");
print("Image uploaded successfully!");
} else {
print("No image selected.");
}
} catch (DioError e) {
if (e.response != null) {
print("Error uploading image: ${e.response?.statusCode} ${e.response?.data}");
} else {
print("Error: ${e.message}");
}
}
}
In this case, DioError is caught if the upload fails, allowing you to check the response status and log any errors.
Conclusion
Using Retrofit in Flutter makes networking and API interactions easier to manage, especially for multipart requests like image uploads. In this article, we’ve walked through how to set up Retrofit ^4.4.1 with Dio, define your API client, and upload images to an API.
With this setup, you can now extend the code to support more complex API operations, such as uploading additional fields, handling errors, and more. Happy coding!
References
https://www.youtube.com/watch?v=ViqtHHLfN94
Originally published on Medium on September 30, 2024.
About the author
- Software Engineer FullStack Developer " nest-js & nuxt js (vue.js) & Flutter"Mobile" "
Comments
Join the discussionNo comments yet. Be the first to comment.