Apiarist
A Dart package for handling API responses in a structured and type-safe way. Apiarist provides a clean abstraction for managing different API response states including loading, success, error, and failure scenarios.
Features
- Type-safe API response handling - Generic
ApiResponse<T>class for any data type - Comprehensive state management - Support for loading, data, error, and failure states
- HTTP status code handling - Built-in support for all standard HTTP status codes
- Riverpod integration - Seamless integration with Riverpod for state management
- Composite responses - Combine multiple parallel API responses with intelligent error handling
- Sequential chaining - Chain dependent API calls with automatic error propagation
- Graceful error handling - Distinguish between API errors and application failures
Installation
Add this to your package's pubspec.yaml file:
dependencies:
apiarist: ^1.0.7
Then run:
dart pub get
Usage
Basic Usage
import 'package:apiarist/apiarist.dart';
// Create different response states
ApiResponse<String> loadingResponse = ApiResponse.loading();
ApiResponse<String> dataResponse = ApiResponse.data("Hello, World!");
ApiResponse<String> errorResponse = ApiResponse.error(ApiError(/* error details */));
ApiResponse<String> failureResponse = ApiResponse.failure(ApiFailure(/* failure details */));
// Handle responses with pattern matching
String result = response.when(
data: (data) => "Success: $data",
loading: () => "Loading...",
error: (error) => "Error: ${error.message}",
failure: (failure) => "Failure: ${failure.message}",
);
Advanced Error Handling
Handle specific HTTP status codes:
String handleResponse = response.when(
data: (data) => "Success: $data",
loading: () => "Loading...",
error: (error) => "Generic error: ${error.message}",
failure: (failure) => "Failure: ${failure.message}",
// Specific HTTP status code handlers
badRequest: (error) => "Bad request: ${error.message}",
unauthorized: (error) => "Please log in again",
forbidden: (error) => "Access denied",
notfound: (error) => "Resource not found",
internalServerError: (error) => "Server error, please try again later",
// ... and many more status codes
);
Composite Responses (Parallel API Calls)
Combine multiple parallel API responses with intelligent error prioritization:
ApiResponse<CombinedData> combinedResponse = ApiResponse.composite([
userResponse,
settingsResponse,
notificationsResponse,
], (responses) {
// All responses are successful, combine the data
return CombinedData(
user: responses[0],
settings: responses[1],
notifications: responses[2],
);
});
Use this when you need to wait for multiple independent API calls to complete before displaying data.
Chaining Responses (Sequential API Calls)
Chain sequential API calls where each call depends on the previous response:
// Fluent chaining syntax
final result = await api
.getUser(userId)
.chain((user) => api.getProfile(user.profileId))
.chain((profile) => api.getSettings(profile.settingsId))
.convertData((settings) => settings.getDetails());
// Handle the final result
result.when(
data: (details) => print("Success: $details"),
loading: () => print("Loading..."),
error: (error) => print("API error: ${error.statusCode}"),
failure: (failure) => print("Failed: ${failure.error}"),
);
The chain() method automatically propagates errors and failures through the chain. If any call fails, subsequent calls are skipped and the error/failure is returned.
Data Conversion
Convert response data to different types:
ApiResponse<User> userResponse = ApiResponse.data(userData);
ApiResponse<String> userNameResponse = userResponse.convertData((user) => user.name);
Checking Response State
if (response.isLoading) {
// Show loading indicator
}
if (response.hasValue) {
// Use response.value
}
if (response.hasError) {
// Handle API error
print("Error: ${response.error?.message}");
}
if (response.hasFailure) {
// Handle application failure
print("Failure: ${response.failure?.message}");
}
Integration with Riverpod
import 'package:riverpod/riverpod.dart';
final userProvider = FutureProvider<ApiResponse<User>>((ref) async {
try {
final response = await apiService.getUser();
return ApiResponse.data(response);
} on ApiError catch (e) {
return ApiResponse.error(e);
} catch (e) {
return ApiResponse.failure(ApiFailure(e.toString()));
}
});
// In your widget
Consumer(
builder: (context, ref, child) {
final userResponse = ref.watch(userProvider);
return userResponse.when(
data: (response) => response.when(
data: (user) => UserWidget(user: user),
loading: () => CircularProgressIndicator(),
error: (error) => ErrorWidget(error: error),
failure: (failure) => FailureWidget(failure: failure),
),
loading: () => CircularProgressIndicator(),
error: (error, stack) => ErrorWidget(error: error),
);
},
)
API Reference
ApiResponse
The main class for handling API responses.
Constructors
ApiResponse.data(T value)- Create a successful response with dataApiResponse.loading()- Create a loading responseApiResponse.error(ApiError error)- Create an error responseApiResponse.failure(ApiFailure failure)- Create a failure response
Properties
bool isLoading- Whether the response is in loading statebool hasValue- Whether the response has dataT? value- The response data (null if not in data state)ApiError? error- The API error (null if not in error state)bool hasError- Whether the response has an errorApiFailure? failure- The failure details (null if not in failure state)bool hasFailure- Whether the response has a failure
Methods
when<R>({...})- Pattern match on the response state with optional specific status code handlersconvertData<R>(R Function(T) converter)- Convert the response data to a different typechain<R>(Future<ApiResponse<R>> Function(T) fn)- Chain sequential API calls with automatic error propagationstatic composite<T>(List<ApiResponse> responses, T Function(List) combiner)- Combine multiple parallel responses
ApiError
Represents an API error with HTTP response details.
ApiFailure
Represents an application-level failure (non-HTTP errors).
Error vs Failure
- ApiError: HTTP-related errors from the server (4xx, 5xx status codes)
- ApiFailure: Application-level failures (network issues, parsing errors, etc.)
Changelog
See CHANGELOG.md for a detailed history of changes.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.