fill
This commit is contained in:
246
lib/core/network/api_response.dart
Normal file
246
lib/core/network/api_response.dart
Normal file
@@ -0,0 +1,246 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Generic API response wrapper that handles the standard API response format
|
||||
///
|
||||
/// All API responses follow this structure:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "Value": T,
|
||||
/// "IsSuccess": bool,
|
||||
/// "IsFailure": bool,
|
||||
/// "Errors": List<String>,
|
||||
/// "ErrorCodes": List<String>
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// final response = ApiResponse.fromJson(
|
||||
/// jsonData,
|
||||
/// (json) => User.fromJson(json),
|
||||
/// );
|
||||
///
|
||||
/// if (response.isSuccess && response.value != null) {
|
||||
/// // Handle success
|
||||
/// final user = response.value!;
|
||||
/// } else {
|
||||
/// // Handle error
|
||||
/// final errorMessage = response.errors.first;
|
||||
/// }
|
||||
/// ```
|
||||
class ApiResponse<T> extends Equatable {
|
||||
/// The actual data/payload of the response
|
||||
/// Can be null if the API call failed or returned no data
|
||||
final T? value;
|
||||
|
||||
/// Indicates if the API call was successful
|
||||
final bool isSuccess;
|
||||
|
||||
/// Indicates if the API call failed
|
||||
final bool isFailure;
|
||||
|
||||
/// List of error messages if the call failed
|
||||
final List<String> errors;
|
||||
|
||||
/// List of error codes for programmatic error handling
|
||||
final List<String> errorCodes;
|
||||
|
||||
const ApiResponse({
|
||||
this.value,
|
||||
required this.isSuccess,
|
||||
required this.isFailure,
|
||||
this.errors = const [],
|
||||
this.errorCodes = const [],
|
||||
});
|
||||
|
||||
/// Create an ApiResponse from JSON
|
||||
///
|
||||
/// The [fromJsonT] function is used to deserialize the "Value" field.
|
||||
/// If null, the value is used as-is.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// // For single object
|
||||
/// ApiResponse.fromJson(json, (j) => User.fromJson(j))
|
||||
///
|
||||
/// // For list of objects
|
||||
/// ApiResponse.fromJson(
|
||||
/// json,
|
||||
/// (j) => (j as List).map((e) => User.fromJson(e)).toList()
|
||||
/// )
|
||||
///
|
||||
/// // For primitive types or no conversion needed
|
||||
/// ApiResponse.fromJson(json, null)
|
||||
/// ```
|
||||
factory ApiResponse.fromJson(
|
||||
Map<String, dynamic> json,
|
||||
T Function(dynamic)? fromJsonT,
|
||||
) {
|
||||
return ApiResponse(
|
||||
value: json['Value'] != null && fromJsonT != null
|
||||
? fromJsonT(json['Value'])
|
||||
: json['Value'] as T?,
|
||||
isSuccess: json['IsSuccess'] ?? false,
|
||||
isFailure: json['IsFailure'] ?? true,
|
||||
errors: json['Errors'] != null
|
||||
? List<String>.from(json['Errors'])
|
||||
: const [],
|
||||
errorCodes: json['ErrorCodes'] != null
|
||||
? List<String>.from(json['ErrorCodes'])
|
||||
: const [],
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a successful response (useful for testing or manual creation)
|
||||
factory ApiResponse.success(T value) {
|
||||
return ApiResponse(
|
||||
value: value,
|
||||
isSuccess: true,
|
||||
isFailure: false,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a failed response (useful for testing or manual creation)
|
||||
factory ApiResponse.failure({
|
||||
required List<String> errors,
|
||||
List<String>? errorCodes,
|
||||
}) {
|
||||
return ApiResponse(
|
||||
isSuccess: false,
|
||||
isFailure: true,
|
||||
errors: errors,
|
||||
errorCodes: errorCodes ?? const [],
|
||||
);
|
||||
}
|
||||
|
||||
/// Check if response has data
|
||||
bool get hasValue => value != null;
|
||||
|
||||
/// Get the first error message if available
|
||||
String? get firstError => errors.isNotEmpty ? errors.first : null;
|
||||
|
||||
/// Get the first error code if available
|
||||
String? get firstErrorCode => errorCodes.isNotEmpty ? errorCodes.first : null;
|
||||
|
||||
/// Get a combined error message from all errors
|
||||
String get combinedErrorMessage {
|
||||
if (errors.isEmpty) return 'An unknown error occurred';
|
||||
return errors.join(', ');
|
||||
}
|
||||
|
||||
/// Convert to a map (useful for serialization or debugging)
|
||||
Map<String, dynamic> toJson(Object? Function(T)? toJsonT) {
|
||||
return {
|
||||
'Value': value != null && toJsonT != null ? toJsonT(value as T) : value,
|
||||
'IsSuccess': isSuccess,
|
||||
'IsFailure': isFailure,
|
||||
'Errors': errors,
|
||||
'ErrorCodes': errorCodes,
|
||||
};
|
||||
}
|
||||
|
||||
/// Create a copy with modified fields
|
||||
ApiResponse<T> copyWith({
|
||||
T? value,
|
||||
bool? isSuccess,
|
||||
bool? isFailure,
|
||||
List<String>? errors,
|
||||
List<String>? errorCodes,
|
||||
}) {
|
||||
return ApiResponse(
|
||||
value: value ?? this.value,
|
||||
isSuccess: isSuccess ?? this.isSuccess,
|
||||
isFailure: isFailure ?? this.isFailure,
|
||||
errors: errors ?? this.errors,
|
||||
errorCodes: errorCodes ?? this.errorCodes,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [value, isSuccess, isFailure, errors, errorCodes];
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (isSuccess) {
|
||||
return 'ApiResponse.success(value: $value)';
|
||||
} else {
|
||||
return 'ApiResponse.failure(errors: $errors, errorCodes: $errorCodes)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension to convert ApiResponse to nullable value easily
|
||||
extension ApiResponseExtension<T> on ApiResponse<T> {
|
||||
/// Get value if success, otherwise return null
|
||||
T? get valueOrNull => isSuccess ? value : null;
|
||||
|
||||
/// Get value if success, otherwise throw exception with error message
|
||||
T get valueOrThrow {
|
||||
if (isSuccess && value != null) {
|
||||
return value!;
|
||||
}
|
||||
throw Exception(combinedErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/// Specialized API response for list data with pagination
|
||||
class PaginatedApiResponse<T> extends ApiResponse<List<T>> {
|
||||
/// Current page number
|
||||
final int currentPage;
|
||||
|
||||
/// Total number of pages
|
||||
final int totalPages;
|
||||
|
||||
/// Total number of items
|
||||
final int totalItems;
|
||||
|
||||
/// Number of items per page
|
||||
final int pageSize;
|
||||
|
||||
/// Whether there is a next page
|
||||
bool get hasNextPage => currentPage < totalPages;
|
||||
|
||||
/// Whether there is a previous page
|
||||
bool get hasPreviousPage => currentPage > 1;
|
||||
|
||||
const PaginatedApiResponse({
|
||||
super.value,
|
||||
required super.isSuccess,
|
||||
required super.isFailure,
|
||||
super.errors,
|
||||
super.errorCodes,
|
||||
required this.currentPage,
|
||||
required this.totalPages,
|
||||
required this.totalItems,
|
||||
required this.pageSize,
|
||||
});
|
||||
|
||||
/// Create a PaginatedApiResponse from JSON
|
||||
factory PaginatedApiResponse.fromJson(
|
||||
Map<String, dynamic> json,
|
||||
List<T> Function(dynamic) fromJsonList,
|
||||
) {
|
||||
final apiResponse = ApiResponse<List<T>>.fromJson(json, fromJsonList);
|
||||
|
||||
return PaginatedApiResponse(
|
||||
value: apiResponse.value,
|
||||
isSuccess: apiResponse.isSuccess,
|
||||
isFailure: apiResponse.isFailure,
|
||||
errors: apiResponse.errors,
|
||||
errorCodes: apiResponse.errorCodes,
|
||||
currentPage: json['CurrentPage'] ?? 1,
|
||||
totalPages: json['TotalPages'] ?? 1,
|
||||
totalItems: json['TotalItems'] ?? 0,
|
||||
pageSize: json['PageSize'] ?? 20,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
...super.props,
|
||||
currentPage,
|
||||
totalPages,
|
||||
totalItems,
|
||||
pageSize,
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user