Simplifying API Calls in Flutter

Bhaveshh Sachala
CodeX
Published in
3 min readApr 26, 2024

In modern app development, interacting with APIs is crucial for fetching data, updating resources, and performing various operations. Flutter, being a versatile framework, offers powerful tools to handle API requests efficiently. One such tool is the ApiService class, designed to streamline API interactions across your Flutter projects.

Introducing ApiService

The ApiService class is a central component that encapsulates API functionalities, making code organization cleaner and API calls consistent throughout the application. It leverages the http package for network requests and dart:convert for JSON serialization.

import 'dart:convert';
import 'dart:developer';
import 'package:http/http.dart' as http;

class ApiService {
Uri _getUri(String endpoint) {
return Uri.parse('${ApiUrl.baseUrl}$endpoint');
}

String _accessToken() {
return "";
}

Map<String, String> _headers() {
return {
"Content-Type": "application/json",
"token": _accessToken(),
};
}

Future<http.Response> _sendRequest(
Future<http.Response> Function() requestFunc, Uri uri,
{dynamic body}) async {
try {
_logRequest(uri, body);
final response = await requestFunc();
_logResponse(uri, response);
if (_isUnauthorized(response.statusCode)) {
_handleUnauthorizedAccess();
}
return response;
} catch (e) {
log("Error during API call to $uri: $e");
rethrow;
}
}

void _logRequest(Uri uri, dynamic body) {
log("Request URL: $uri");
if (body != null) log("Request Body: ${jsonEncode(body)}");
}

void _logResponse(Uri uri, http.Response response) {
log("Response for URL: $uri");
log("Status Code: ${response.statusCode}");
log("Response Body: ${response.body}");
}

bool _isUnauthorized(int statusCode) {
return statusCode == 401 || statusCode == 403;
}

void _handleUnauthorizedAccess() {
log("Unauthorized access detected.");
}

Future<http.Response> get(String endpoint) async {
Uri uri = _getUri(endpoint);
return _sendRequest(() => http.get(uri, headers: _headers()), uri);
}

Future<http.Response> post(String endpoint, dynamic body) async {
Uri uri = _getUri(endpoint);
return _sendRequest(
() => http.post(uri, headers: _headers(), body: jsonEncode(body)), uri,
body: body);
}

Future<http.Response> put(String endpoint, dynamic body) async {
Uri uri = _getUri(endpoint);
return _sendRequest(
() => http.put(uri, headers: _headers(), body: jsonEncode(body)), uri,
body: body);
}

Future<http.Response> patch(String endpoint, dynamic body) async {
Uri uri = _getUri(endpoint);
return _sendRequest(
() => http.patch(uri, headers: _headers(), body: jsonEncode(body)), uri,
body: body);
}

Future<http.Response> delete(String endpoint, {dynamic body}) async {
Uri uri = _getUri(endpoint);
return _sendRequest(
() => http.delete(uri, headers: _headers(), body: jsonEncode(body)),
uri,
body: body);
}

Future<http.Response> updateProfileImage(String imagePath, endpoint) async {
final uri = _getUri(endpoint);
var request = http.MultipartRequest('PATCH', uri);
request.files.add(await http.MultipartFile.fromPath('image', imagePath));
request.headers.addAll(_headers());

_logRequest(uri, {"imagePath": imagePath});

var streamedResponse = await request.send();
var response = await http.Response.fromStream(streamedResponse);
_logResponse(uri, response);

return response;
}
}

Initializing the ApiService

To start using ApiService, you need to create an instance of it in your project. Typically, this is done at the initialization stage of your app.

ApiService apiService = ApiService();

GET Request Example

Fetching data from an API endpoint is a common operation. Here’s how you can use ApiService for a GET request:

Future<void> fetchUserData() async {
final response = await apiService.get('/users');
// Process the response data here…
}

POST Request Example

Creating or updating resources often involves sending data to the server. Let’s see how a POST request can be made using ApiService:

Future<void> createUser(Map<String, dynamic> userData) async {
final response = await apiService.post('/users', userData);
// Handle the response accordingly...
}

PUT Request Example

When updating existing resources, a PUT request is typically used. Here’s an example of using ApiService for a PUT request:

Future<void> updateUser(String userId, Map<String, dynamic> updatedData) async {
final response = await apiService.put('/users/$userId', updatedData);
// Process the response as needed...
}

PATCH Request Example

Similar to PUT requests, PATCH requests are used for partial updates. Here’s how you can utilize ApiService for a PATCH request:

Future<void> updateProfile(String userId, Map<String, dynamic> profileData) async {
final response = await apiService.patch('/users/$userId/profile', profileData);
// Handle the response accordingly...
}

DELETE Request Example

Deleting resources is another common operation. Here’s how you can perform a DELETE request using ApiService:

Future<void> deleteUser(String userId) async {
final response = await apiService.delete('/users/$userId');
// Process the response as needed...
}

File Upload Example

Uploading files, such as images, is often required in apps. ApiService can handle file uploads seamlessly. Here's an example:

Future<void> uploadProfileImage(String imagePath, String userId) async {
final response = await apiService.updateProfileImage(imagePath, '/users/$userId/avatar');
// Handle the upload response...
}

The ApiService class provides a structured approach to handling API requests in Flutter apps. By encapsulating common HTTP methods and providing consistent error handling, it simplifies the integration of backend services with your frontend UI. Whether it's fetching data, updating resources, or uploading files, ApiService streamlines the communication between your Flutter app and the server, making your codebase more manageable and maintainable.

--

--

Bhaveshh Sachala
CodeX
Writer for

👨‍💻 Passionate Flutter & iOS developer creating delightful mobile experiences with clean code and pixel-perfect UI. Let's build something amazing!