Last active
August 25, 2024 20:20
-
-
Save EsinShadrach/a0fe5eb60ca1bf5a722975308b937fdc to your computer and use it in GitHub Desktop.
Handling API calls with status code in flutter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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."; | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Separate status code handler in a different files and add necessary imports