Skip to content

Commit

Permalink
Add nullable map and future extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
ashtanko committed Jul 18, 2024
1 parent 8fe5d05 commit 1cf927e
Show file tree
Hide file tree
Showing 9 changed files with 453 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.1.7
- Add nullable map extensions
- Add nullable future extensions

## 0.1.6
- Add `filterNotNullTo`, `filterNotNull`, `listOfNotNull`, `ifNull` functions and extensions
- Add unit tests
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,18 @@ void main() {
}
```

```dart
void main() {
final Map<String, int>? nullableMap = {'a': 1, 'b': 2};
print(nullableMap.isNullOrEmpty); // false
print(nullableMap.getOrElse('c', 0)); // 0
nullableMap.putIfAbsentOrElse('c', 3); // {a: 1, b: 2, c: 3}
nullableMap.updateValue('a', (value) => value! + 10); // {a: 11, b: 2, c: 3}
final filteredMap = nullableMap.filter((entry) => entry.value > 2);
print(filteredMap); // {a: 11, c: 3}
}
```

## Contributing

Contributions are welcome! Please read the contributing guide to learn how to contribute to the project and set up a development environment.
Expand Down
105 changes: 104 additions & 1 deletion example/nullx_example.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:nullx/nullx.dart';

void main() {
void main() async {
/// Variables
// ignore: unnecessary_nullable_for_final_variable_declarations
Expand All @@ -13,6 +13,12 @@ void main() {
const String? nullString = null;
const double? nullDouble = null;
const bool? nullBool = null;
// ignore: unnecessary_nullable_for_final_variable_declarations
final Map<String, int>? nullableMap = {'a': 1, 'b': 2};
// ignore: unnecessary_nullable_for_final_variable_declarations
final Future<int?>? nullableFuture = Future.value(42);
// ignore: unnecessary_nullable_for_final_variable_declarations
final Future<int?>? failedFuture = Future.error(Exception('Failed'));

// ignore: unnecessary_nullable_for_final_variable_declarations
final List<int?>? nullableIntList = [1, null, 3, null];
Expand Down Expand Up @@ -183,6 +189,103 @@ void main() {
// Performs an operation on the age if it's not null
age.let((a) => a);

// Check if the map is null or empty
// ignore: avoid_print
print(nullableMap.isNullOrEmpty); // false

// Get value for key or return default
// ignore: avoid_print
print(nullableMap.getOrElse('c', 0)); // 0

// Put a value if the key is absent
nullableMap.putIfAbsentOrElse('c', 3);
// ignore: avoid_print
print(nullableMap); // {a: 1, b: 2, c: 3}

// Update a value using a function
nullableMap.updateValue('a', (value) => value! + 10);
// ignore: avoid_print
print(nullableMap); // {a: 11, b: 2, c: 3}
// ignore: avoid_print

// Filter the map
final filteredMap = nullableMap.filter((entry) => entry.value > 2);
// ignore: avoid_print
print(filteredMap); // {a: 11, c: 3}

// Map keys and values
final mappedMap = nullableMap.mapKeysAndValues(
(entry) => MapEntry(entry.key.toUpperCase(), entry.value.toString()),
);
// ignore: avoid_print
print(mappedMap); // {A: 11, B: 2, C: 3}

// Iterate through the map
// ignore: avoid_print
nullableMap.forEachEntry((key, value) => print('$key: $value'));
// Output:
// a: 11
// b: 2
// c: 3

// Check if the map contains a key or value
// ignore: avoid_print
print(nullableMap.containsKeyOrNull('a')); // true
// ignore: avoid_print
print(nullableMap.containsValueOrNull(4)); // false

// Return a default value if the Future completes with null
final int result2 = await nullableFuture.orDefault(5);
// ignore: avoid_print
print(result2); // 42

// Return null if the Future completes with an error
final int? errorHandled = await failedFuture.onErrorReturnNull();
// ignore: avoid_print
print(errorHandled); // null

// Return a default value if the Future completes with an error
final int? errorHandledWithValue = await failedFuture.onErrorReturn(5);
// ignore: avoid_print
print(errorHandledWithValue); // 5

// Provide an alternative Future if the original completes with null
// ignore: unnecessary_nullable_for_final_variable_declarations
final Future<int?>? nullableFutureWithNull = Future.value();
final int alternative = await nullableFutureWithNull.orElse(() async => 99);
// ignore: avoid_print
print(alternative); // 99

// Execute an action when the Future completes
// ignore: avoid_print
await nullableFuture.whenComplete(() => print('Completed')); // Completed

// Ignore any errors the Future may throw
await failedFuture.ignoreErrors(); // No output, error ignored

// Timeout a Future and return null if it doesn't complete in time
// ignore: unnecessary_nullable_for_final_variable_declarations
final Future<int?>? slowFuture =
Future.delayed(const Duration(seconds: 2), () => 10);
final int? timedOut =
await slowFuture.timeoutWithNull(const Duration(seconds: 1));
// ignore: avoid_print
print(timedOut); // null

// Chain another Future using thenOrNull
final Future<String?> chained =
nullableFuture.thenOrNull((value) => Future.value('Value: $value'));
// ignore: avoid_print
print(await chained); // Value: 42

// Catch an error and return null using catchErrorOrNull
final int? caughtError = await failedFuture.catchErrorOrNull((error) {
// ignore: avoid_print
print('Caught error: $error');
});
// ignore: avoid_print
print(caughtError); // Caught error: Exception: Failed, null

// Throws a [NotImplementedError] indicating that an operation is
try {
todo();
Expand Down
2 changes: 2 additions & 0 deletions lib/nullx.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ library nullx;

export 'src/collections.dart';
export 'src/exception.dart';
export 'src/future.dart';
export 'src/map.dart';
export 'src/types.dart';
export 'src/utils.dart';
89 changes: 89 additions & 0 deletions lib/src/future.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import 'dart:async';

/// Extension on `Future<T?>?` providing additional null-aware and error
/// handling operations.
///
/// This extension adds methods to nullable `Future<T?>` objects, allowing for
/// more expressive handling of asynchronous operations that could result in
/// null or error states. It includes methods for providing default values,
/// handling errors gracefully, and more nuanced manipulations like executing
/// alternative futures or suppressing errors.
extension NullableFutureExtensions<T> on Future<T?>? {
/// Returns the future's value if not null; otherwise, returns a default value
Future<T> orDefault(T defaultValue) async => (await this) ?? defaultValue;

/// Returns the future's value, or null if the future itself is null.
Future<T?> orNull() async => await this;

/// Attempts to return the future's value; on error, returns a specified value
Future<T?> onErrorReturn(T value) async {
try {
return await this;
} catch (e) {
return value;
}
}

/// Attempts to return the future's value; on error, returns null.
Future<T?> onErrorReturnNull() async {
try {
return await this;
} catch (e) {
return null;
}
}

/// Returns the future's value if not null; otherwise, executes an alternative
/// future.
Future<T> orElse(Future<T> Function() alternative) async {
return (await this) ?? await alternative();
}

/// Executes a specified action when the future completes, regardless of the
/// outcome.
Future<T?> whenComplete(Function() action) async {
try {
return await this;
} finally {
action();
}
}

/// Suppresses any errors that occur during the future's execution.
Future<void> ignoreErrors() async {
try {
await this;
} catch (_) {}
}

/// Returns null if the future does not complete within a specified duration.
Future<T?> timeoutWithNull(Duration duration) async {
try {
return await this?.timeout(duration);
} catch (e) {
return null;
}
}

/// Applies a transformation to the future's value if not null.
Future<R?> thenOrNull<R>(FutureOr<R?> Function(T?) onValue) async {
return this == null ? null : await this!.then(onValue);
}

/// Attempts to return the future's value; on error, executes an onError
/// function and returns null.
Future<T?> catchErrorOrNull(
Function(Object) onError, {
bool Function(Object)? test,
}) async {
try {
return await this;
} catch (e) {
if (test == null || test(e)) {
onError(e);
return null;
}
rethrow;
}
}
}
63 changes: 63 additions & 0 deletions lib/src/map.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/// Extension on `Map<K, V>?` providing additional null-aware operations.
///
/// This extension adds methods to `Map` objects that allow for more expressive
/// handling of operations that could involve `null` maps. It includes methods
/// for checking if a map is null or empty, providing default values, and more
/// nuanced manipulations like filtering, updating, and transforming maps safely
extension NullableMapExtensions<K, V> on Map<K, V>? {
/// Checks if the map is null or empty.
bool get isNullOrEmpty => this == null || this!.isEmpty;

/// Checks if the map is not null and not empty.
bool get isNotNullOrEmpty => !isNullOrEmpty;

/// Returns the map if it's not null, otherwise returns the provided default
/// value.
Map<K, V> orDefault(Map<K, V> defaultValue) => this ?? defaultValue;

/// Returns the value for the given key if it exists, otherwise returns the
/// provided default value.
V getOrElse(K key, V defaultValue) => this?[key] ?? defaultValue;

/// Puts the default value for the given key if the key is absent in the map.
void putIfAbsentOrElse(K key, V defaultValue) {
if (this != null) {
this!.putIfAbsent(key, () => defaultValue);
}
}

/// Updates the value for the given key using the provided update function.
void updateValue(K key, V Function(V?) update) {
if (this != null) {
this![key] = update(this![key]);
}
}

/// Returns a new map containing the entries that satisfy the provided test.
Map<K, V> filter(bool Function(MapEntry<K, V>) test) {
if (this == null) return {};
return Map<K, V>.fromEntries(this!.entries.where(test));
}

/// Maps the keys and values of the map using the provided convert function
/// and returns a new map.
Map<RK, RV> mapKeysAndValues<RK, RV>(
MapEntry<RK, RV> Function(MapEntry<K, V>) convert,
) {
if (this == null) return {};
return Map<RK, RV>.fromEntries(this!.entries.map(convert));
}

/// Performs an action for each key-value pair in the map.
void forEachEntry(void Function(K key, V value) action) {
if (this != null) {
this!.forEach(action);
}
}

/// Checks if a key exists in the map, safely handling null maps.
bool containsKeyOrNull(K key) => this?.containsKey(key) ?? false;

/// Checks if a value exists in the map, safely handling null maps.
bool containsValueOrNull(V value) => this?.containsValue(value) ?? false;
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: nullx
homepage: https://shtanko.dev
description: >-
nullx is a collection of elegant extensions for handling null types in Dart.
version: 0.1.6
version: 0.1.7
repository: https://github.com/ashtanko/nullx

topics:
Expand Down
Loading

0 comments on commit 1cf927e

Please sign in to comment.