flutter_operations 1.5.0
flutter_operations: ^1.5.0 copied to clipboard
Type-safe async operation state management for Flutter using sealed classes and exhaustive pattern matching.
1.5.0 #
New Features #
- Promoted
dataOrNullgetter toOperationStatebase class — Previously only available onSuccessOperation,dataOrNullis now accessible on all state types (LoadingOperation,IdleOperation,ErrorOperation,SuccessOperation). This allows safe nullable data access without pattern-matching first. ForSuccessOperation.empty()states, it returnsnullinstead of throwing like thedatagetter does.
Improvements #
- Replaced
print()withdeveloper.log()in defaultonErrorhandlers — BothAsyncOperationMixinandStreamOperationMixinnow usedart:developer'slog()for default error logging. This integrates with Flutter DevTools, provides structured metadata (error object, stack trace, category name), and is automatically filtered out in release builds. Zero new dependencies. - Fixed
analysis_options.yaml— Now correctly usespackage:flutter_lints/flutter.yamlto match theflutter_lintsdev dependency, enabling Flutter-specific lint rules. - Improved dual-override validation comments — Added clarifying comments explaining why the
fetch()/stream()validation call is side-effect-free in the happy path. - Added doc comment for nullable
Tedge case onSuccessOperation— Documents the behavior whenTitself is nullable (e.g.,SuccessOperation<String?>(data: null)).
Bug Fixes #
- Fixed
_NotImplementedException.toStringinStreamOperationMixin— Was incorrectly displayingAsyncOperationMixinExceptioninstead ofStreamOperationMixinException. - Made
idleparameter functional inStreamOperationMixin.setLoading— The parameter was previously accepted but never used. NowsetLoading(idle: true)correctly produces anIdleOperationand invokes theonIdlecallback. - Fixed
Product.examples()in example app —Random().nextInt(3)only selected from 3 of 9 categories. Now usesrandom.nextInt(categories.length)with a singleRandominstance. - Fixed timer leak in
AdvancedCustomHandlersExample— Addeddispose()override to cancel_retryTimerand_circuitBreakerTimer, preventing callbacks firing on unmounted widgets. - Fixed
BasicStreamExamplebuilder — Now uses thevalueparameter fromValueListenableBuilderinstead of readingoperationdirectly.
1.4.0 #
BREAKING CHANGES #
SuccessOperation.empty()no longer accepts adataparameter - The constructor now always creates a truly empty state. Previously, passingdatawould create a non-empty state withempty = false, which was confusing.
Bug Fixes #
- Fixed crash when comparing empty
SuccessOperationstates - The==operator andhashCodenow use the internal_datafield instead of calling the throwingdatagetter. This fixes issues with Bloc/Cubit state comparison when emittingSuccessOperation.empty(). - Fixed
hasData/hasNoDatagetters throwing on empty operations - These now safely check the internal field. - Fixed
toString()for empty operations - No longer throws when converting empty states to string.
New Features #
- Added
dataOrNullgetter toSuccessOperation- Provides safe nullable access to data without throwing. Use this when you're unsure if the operation is empty, or in contexts where you want to handle both cases uniformly.
Migration #
If you were using SuccessOperation.empty(data: someValue), this will no longer compile. This usage was semantically
incorrect - use SuccessOperation(data: someValue) instead for non-empty states.
// Before (incorrect usage that will no longer compile):
SuccessOperation.empty
(
data: myData) // ❌ Removed
// After (correct usage):
SuccessOperation(data: myData) // ✅ Use this for non-empty
SuccessOperation.
empty
(
) // ✅ Use this for truly empty
1.3.0 #
BREAKING CHANGES #
- Removed
OperationResult<T>class - Replaced with Dart records(T, String?)for less cpu and memory churn. fetchWithMessage()now returnsFutureOr<(T, String?)>instead ofFutureOr<OperationResult<T>>.streamWithMessage()now returnsStream<(T, String?)>instead ofStream<OperationResult<T>>.
Migration #
If you're using fetchWithMessage() or streamWithMessage(), update your code:
Before (1.2.0):
@override
Future<OperationResult<User>> fetchWithMessage() async {
final user = User.fromJson(json['data']);
final message = json['message'] as String?;
return OperationResult(user, message: message);
}
After (1.3.0):
@override
Future<(User, String?)> fetchWithMessage() async {
final user = User.fromJson(json['data']);
final message = json['message'] as String?;
return (user, message);
}
Before (1.2.0) - Streams:
@override
Stream<OperationResult<Message>> streamWithMessage() {
return messageStream.map((jsonMap) {
final data = Message.fromJson(jsonMap['data']);
final message = jsonMap['message'] as String?;
return OperationResult(data, message: message);
});
}
After (1.3.0) - Streams:
@override
Stream<(Message, String?)> streamWithMessage() {
return messageStream.map((jsonMap) {
final data = Message.fromJson(jsonMap['data']);
final message = jsonMap['message'] as String?;
return (data, message);
});
}
The behavior remains the same - the only change is the API surface. All other functionality, including message handling
in SuccessOperation, works exactly as before.
1.2.0 #
New #
- Added
OperationResult<T>class to hold data with optional success messages. - Added
fetchWithMessage()method toAsyncOperationMixinfor returning data with messages. - Added
streamWithMessage()method toStreamOperationMixinfor streams with messages. - Added optional
messagefield toSuccessOperation<T>for success-related information. - Updated
setSuccess()andsetData()methods to accept optionalmessageparameter.
Changed #
fetch()andfetchWithMessage()are now both optional - exactly one must be overridden.stream()andstreamWithMessage()are now both optional - exactly one must be overridden.- Smart method detection: tries
*WithMessage()first, falls back to standard method. - Throws an error messages when neither or both methods are overridden.
Usage #
// Simple case - no message
@override
Future<User> fetch() async => api.getUser();
// With message - use fetchWithMessage()
@override
Future<OperationResult<User>> fetchWithMessage() async {
// API returns a Map with 'data' and 'message' fields
final response = await http.get(Uri.parse('https://api.example.com/user'));
final json = jsonDecode(response.body);
// Decode the data
final user = User.fromJson(json['data'];
// Extract the message from server response
final message = json['message'] as String?;
return OperationResult(user, message: message);
}
Migration: #
- Existing code using
fetch()continues to work without changes. - To add success messages, override
fetchWithMessage()instead offetch(). - Access messages in pattern matching:
SuccessOperation(:var data, :var message?).
1.1.1 #
- Address format warnings.
1.2.0 #
New #
- Added
OperationResult<T>class to hold data with optional success messages. - Added
fetchWithMessage()method toAsyncOperationMixinfor returning data with messages. - Added
streamWithMessage()method toStreamOperationMixinfor streams with messages. - Added optional
messagefield toSuccessOperation<T>for success-related information. - Updated
setSuccess()andsetData()methods to accept optionalmessageparameter.
Changed #
fetch()andfetchWithMessage()are now both optional - exactly one must be overridden.stream()andstreamWithMessage()are now both optional - exactly one must be overridden.- Smart method detection: tries
*WithMessage()first, falls back to standard method. - Throws an error messages when neither or both methods are overridden.
Usage #
// Simple case - no message
@override
Future<User> fetch() async => api.getUser();
// With message - use fetchWithMessage()
@override
Future<OperationResult<User>> fetchWithMessage() async {
// API returns a Map with 'data' and 'message' fields
final response = await http.get(Uri.parse('https://api.example.com/user'));
final json = jsonDecode(response.body);
// Decode the data
final user = User.fromJson(json['data'];
// Extract the message from server response
final message = json['message'] as String?;
return OperationResult(user, message: message);
}
Migration: #
- Existing code using
fetch()continues to work without changes. - To add success messages, override
fetchWithMessage()instead offetch(). - Access messages in pattern matching:
SuccessOperation(:var data, :var message?).
1.1.1 #
- Address format warnings.
1.1.0 #
BREAKING CHANGES: #
- Removed
idleparameter fromLoadingOperation - Added
IdleOperation<T>class extendingLoadingOperation<T> - Changed
LoadingOperationfromfinaltobaseclass - Added convenience getters:
hasNoData,isLoading,isIdle,isSuccess,isError, etc. - Added
SuccessOperation.empty()constructor andemptyproperty - Added
setIdle()method to both mixins - Removed
doesGlobalRefreshparameter from internal methods
Migration:
- Replace
LoadingOperation.idlechecks withoperation.isIdle - Handle
IdleOperationin pattern matching whenloadOnInit = false - Update equality checks due to
LoadingOperationstructure changes
1.0.1 #
- Update README.md
1.0.0 #
- Initial release.
