Skip to content

Instantly share code, notes, and snippets.

@EsinShadrach
Last active August 25, 2024 20:20
Show Gist options
  • Select an option

  • Save EsinShadrach/a0fe5eb60ca1bf5a722975308b937fdc to your computer and use it in GitHub Desktop.

Select an option

Save EsinShadrach/a0fe5eb60ca1bf5a722975308b937fdc to your computer and use it in GitHub Desktop.
Handling API calls with status code in flutter
part of "auth/auth.dart";
/// A typedef for a function that deserializes JSON into an object of type [T].
///
/// Example usage:
/// ```dart
/// User userFromJson(Map<String, dynamic> json) => User.fromJson(json);
/// ```
typedef Serializer<T> = T Function(Map<String, dynamic> data);
/// A mixin to store API constants, such as the base URI.
mixin ApiConstants {
static const String baseUri = "38.180.97.128";
static int port = 8080;
}
/// An abstract class representing a JSON serializable entity.
///
/// Classes that implement this interface must provide an implementation
/// for the [toJson] method, allowing the object to be converted to a JSON map.
///
/// Example:
/// ```dart
/// class User implements JsonSerializable {
/// final String name;
/// final int age;
///
/// User({required this.name, required this.age});
///
/// @override
/// Map<String, dynamic> toJson() {
/// return {
/// 'name': name,
/// 'age': age,
/// };
/// }
///
/// static User fromJson(Map<String, dynamic> json) {
/// return User(
/// name: json['name'],
/// age: json['age'],
/// );
/// }
/// }
/// ```
abstract class JsonSerializable {
/// Converts the object into a JSON map.
Map<String, dynamic> toJson();
}
/// A utility class for making API requests.
///
/// This class provides methods for making POST and GET requests, handling
/// the serialization of JSON data using the [JsonSerializable] interface,
/// or directly from a basic map.
class ApiUtil with StatusCodes {
/// Sends a POST request to the specified [path] with the given [data].
///
/// The [data] can be a [JsonSerializable] object or a basic map.
/// The response is deserialized using the provided [fromJson] function.
///
/// Additional [headers] can be passed in as a [Map].
///
/// Returns the deserialized response object of type [T].
///
/// Throws an [Exception] if the request fails.
///
/// Example usage:
/// ```dart
/// final user = User(name: "John", age: 30);
/// final newUser = await ApiUtil.post<User>(
/// '/users',
/// {'Authorization': 'Bearer token'},
/// user,
/// User.fromJson,
/// );
/// ```
static Future<T> post<T>({
required String path,
Map<String, String>? headers,
dynamic data,
Map<String, dynamic>? queryParameters,
required Serializer<T> fromJson,
}) async {
debugPrint("Posting data");
var internalHeaders = {
"Content-Type": "application/json",
};
if (headers != null) {
internalHeaders.addAll(internalHeaders);
}
debugPrint("$internalHeaders");
Uri uri = Uri(
scheme: "http",
host: ApiConstants.baseUri,
path: path,
port: ApiConstants.port,
queryParameters: queryParameters,
);
debugPrint(uri.toString());
final response = await http.post(
uri,
headers: internalHeaders,
body: jsonEncode(data is JsonSerializable ? data.toJson() : data),
);
debugPrint("Res Body: ${response.body}");
int statusCode = jsonDecode(response.body)["code"];
debugPrint(statusCode.toString());
if (StatusCodes.isSuccess(statusCode)) {
return fromJson(jsonDecode(response.body));
} else {
// Check if backend has a custom error message if not send myClientErrorMessage
String? msg = jsonDecode(response.body)["message"];
if (msg != null) {
throw msg;
}
final errorMessage = StatusCodes.clientErrorMessage(statusCode);
debugPrint("Error: $errorMessage");
throw errorMessage;
}
}
/// Sends a GET request to the specified [path].
///
/// The response is deserialized using the provided [fromJson] function.
///
/// Additional [headers] can be passed in as a [Map].
///
/// Returns the deserialized response object of type [T].
///
/// Throws an [Exception] if the request fails.
///
/// Example usage:
/// ```dart
/// final user = await ApiUtil.get<User>(
/// '/users/1',
/// {'Authorization': 'Bearer token'},
/// User.fromJson,
/// );
/// ```
static Future<T> get<T>({
required String path,
Map<String, String>? headers,
Map<String, dynamic>? queryParameters,
required Serializer<T> fromJson,
}) async {
final response = await http.get(
Uri(
scheme: "http",
host: ApiConstants.baseUri,
path: path,
port: ApiConstants.port,
queryParameters: queryParameters,
),
headers: headers,
);
debugPrint("Res Body: ${response.body}");
int statusCode = jsonDecode(response.body)["code"];
debugPrint(statusCode.toString());
if (StatusCodes.isSuccess(statusCode)) {
return fromJson(jsonDecode(response.body));
} else {
final errorMessage = StatusCodes.clientErrorMessage(statusCode);
debugPrint("Error: $errorMessage");
throw errorMessage;
}
}
/// Sends a PUT request to the specified [path] with the given [data].
///
/// The [data] can be a [JsonSerializable] object or a basic map.
/// The response is deserialized using the provided [fromJson] function.
///
/// Additional [headers] can be passed in as a [Map].
///
/// Returns the deserialized response object of type [T].
///
/// Throws an [Exception] if the request fails.
///
/// Example usage:
/// ```dart
/// final user = User(name: "John", age: 30);
/// final newUser = await ApiUtil.put<User>(
/// '/users',
/// {'Authorization': 'Bearer token'},
/// user,
/// User.fromJson,
/// );
/// ```
static Future<T> put<T>({
required String path,
Map<String, String>? headers,
dynamic data,
Map<String, dynamic>? queryParameters,
required Serializer<T> fromJson,
}) async {
debugPrint("Posting data");
var internalHeaders = {
"Content-Type": "application/json",
};
if (headers != null) {
internalHeaders.addAll(internalHeaders);
}
debugPrint("$internalHeaders");
Uri uri = Uri(
scheme: "http",
host: ApiConstants.baseUri,
path: path,
port: ApiConstants.port,
queryParameters: queryParameters,
);
debugPrint(uri.toString());
final response = await http.put(
uri,
headers: internalHeaders,
body: jsonEncode(data is JsonSerializable ? data.toJson() : data),
);
debugPrint("Res Body: ${response.body}");
int statusCode = jsonDecode(response.body)["code"];
debugPrint(statusCode.toString());
if (StatusCodes.isSuccess(statusCode)) {
return fromJson(jsonDecode(response.body));
} else {
final errorMessage = StatusCodes.clientErrorMessage(statusCode);
debugPrint("Error: $errorMessage");
throw errorMessage;
}
}
}
/// A mixin to handle and categorize HTTP status codes.
///
/// This mixin provides constants for various HTTP status codes, methods
/// to check the type of status code (success, client error, server error),
/// and methods to generate client-friendly error messages.
///
/// Example usage:
/// ```dart
/// if (StatusCodes.isSuccess(statusCode)) {
/// // Handle successful response
/// } else if (StatusCodes.isClientError(statusCode)) {
/// // Handle client error
/// } else if (StatusCodes.isServerError(statusCode)) {
/// // Handle server error
/// } else if (StatusCodes.isNetworkError(statusCode)) {
/// // Handle network error
/// } else if (StatusCodes.isTimeoutError(statusCode)) {
/// // Handle timeout error
/// }
///
/// String message = StatusCodes.clientErrorMessage(statusCode);
/// ```
mixin StatusCodes {
/// HTTP status code for a successful request.
static const int ok = 200;
/// HTTP status code for a resource successfully created.
static const int created = 201;
/// HTTP status code for a request accepted but not yet processed.
static const int accepted = 202;
/// HTTP status code indicating no content in the response.
static const int noContent = 204;
/// HTTP status code indicating a bad request.
static const int badRequest = 400;
/// HTTP status code indicating unauthorized access.
static const int unauthorized = 401;
/// HTTP status code indicating access forbidden.
static const int forbidden = 403;
/// HTTP status code indicating the requested resource was not found.
static const int notFound = 404;
/// HTTP status code indicating a conflict with the current state.
static const int conflict = 409;
/// HTTP status code indicating an internal server error.
static const int internalServerError = 500;
/// HTTP status code indicating the server does not support the functionality.
static const int notImplemented = 501;
/// HTTP status code indicating an invalid response from an upstream server.
static const int badGateway = 502;
/// HTTP status code indicating the server is currently unavailable.
static const int serviceUnavailable = 503;
/// HTTP status code indicating a gateway timeout.
static const int gatewayTimeout = 504;
/// HTTP status code indicating the HTTP version is not supported.
static const int httpVersionNotSupported = 505;
/// HTTP status code indicating a network connection timeout.
static const int networkConnectTimeoutError = 599;
/// List of successful HTTP status codes.
static const List<int> success = [ok, created, accepted, noContent];
/// List of client error HTTP status codes.
static const List<int> clientError = [
badRequest,
unauthorized,
forbidden,
notFound,
conflict
];
/// List of server error HTTP status codes.
static const List<int> serverError = [
internalServerError,
notImplemented,
badGateway,
serviceUnavailable,
gatewayTimeout,
httpVersionNotSupported,
networkConnectTimeoutError
];
/// Checks if the provided status code indicates a successful response.
///
/// Returns `true` if the status code is in the [success] list.
static bool isSuccess(int statusCode) => success.contains(statusCode);
/// Checks if the provided status code indicates a client error.
///
/// Returns `true` if the status code is in the [clientError] list.
static bool isClientError(int statusCode) => clientError.contains(statusCode);
/// Checks if the provided status code indicates a server error.
///
/// Returns `true` if the status code is in the [serverError] list.
static bool isServerError(int statusCode) => serverError.contains(statusCode);
/// Checks if the provided status code indicates a network error.
///
/// Returns `true` if the status code is [networkConnectTimeoutError].
static bool isNetworkError(int statusCode) =>
statusCode == networkConnectTimeoutError;
/// Checks if the provided status code indicates a timeout error.
///
/// Returns `true` if the status code is [gatewayTimeout] or [networkConnectTimeoutError].
static bool isTimeoutError(int statusCode) =>
statusCode == gatewayTimeout || statusCode == networkConnectTimeoutError;
/// Provides a client-friendly error message based on the status code.
///
/// Returns a descriptive error message based on whether the status code
/// indicates a network error, client error, timeout error, or server error.
///
/// Example:
/// ```dart
/// String message = StatusCodes.clientErrorMessage(statusCode);
/// ```
static String clientErrorMessage(int statusCode) {
if (statusCode == 404) {
return "Not Found";
}
if (isNetworkError(statusCode)) {
return "A network error occurred, please check your connection.";
}
if (isClientError(statusCode)) {
return "An error occurred, please try again.";
}
if (isTimeoutError(statusCode)) {
return "The request timed out, please try again.";
}
if (isServerError(statusCode)) {
return "An error occurred on our end, please try again.";
}
return "An error occurred, please try again.";
}
}
@EsinShadrach

Copy link
Copy Markdown
Author

Separate status code handler in a different files and add necessary imports

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment