Skip to content

Commit 1cf927e

Browse files
committed
Add nullable map and future extensions
1 parent 8fe5d05 commit 1cf927e

File tree

9 files changed

+453
-2
lines changed

9 files changed

+453
-2
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 0.1.7
4+
- Add nullable map extensions
5+
- Add nullable future extensions
6+
37
## 0.1.6
48
- Add `filterNotNullTo`, `filterNotNull`, `listOfNotNull`, `ifNull` functions and extensions
59
- Add unit tests

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,18 @@ void main() {
195195
}
196196
```
197197

198+
```dart
199+
void main() {
200+
final Map<String, int>? nullableMap = {'a': 1, 'b': 2};
201+
print(nullableMap.isNullOrEmpty); // false
202+
print(nullableMap.getOrElse('c', 0)); // 0
203+
nullableMap.putIfAbsentOrElse('c', 3); // {a: 1, b: 2, c: 3}
204+
nullableMap.updateValue('a', (value) => value! + 10); // {a: 11, b: 2, c: 3}
205+
final filteredMap = nullableMap.filter((entry) => entry.value > 2);
206+
print(filteredMap); // {a: 11, c: 3}
207+
}
208+
```
209+
198210
## Contributing
199211

200212
Contributions are welcome! Please read the contributing guide to learn how to contribute to the project and set up a development environment.

example/nullx_example.dart

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import 'package:nullx/nullx.dart';
22

3-
void main() {
3+
void main() async {
44
/// Variables
55
66
// ignore: unnecessary_nullable_for_final_variable_declarations
@@ -13,6 +13,12 @@ void main() {
1313
const String? nullString = null;
1414
const double? nullDouble = null;
1515
const bool? nullBool = null;
16+
// ignore: unnecessary_nullable_for_final_variable_declarations
17+
final Map<String, int>? nullableMap = {'a': 1, 'b': 2};
18+
// ignore: unnecessary_nullable_for_final_variable_declarations
19+
final Future<int?>? nullableFuture = Future.value(42);
20+
// ignore: unnecessary_nullable_for_final_variable_declarations
21+
final Future<int?>? failedFuture = Future.error(Exception('Failed'));
1622

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

192+
// Check if the map is null or empty
193+
// ignore: avoid_print
194+
print(nullableMap.isNullOrEmpty); // false
195+
196+
// Get value for key or return default
197+
// ignore: avoid_print
198+
print(nullableMap.getOrElse('c', 0)); // 0
199+
200+
// Put a value if the key is absent
201+
nullableMap.putIfAbsentOrElse('c', 3);
202+
// ignore: avoid_print
203+
print(nullableMap); // {a: 1, b: 2, c: 3}
204+
205+
// Update a value using a function
206+
nullableMap.updateValue('a', (value) => value! + 10);
207+
// ignore: avoid_print
208+
print(nullableMap); // {a: 11, b: 2, c: 3}
209+
// ignore: avoid_print
210+
211+
// Filter the map
212+
final filteredMap = nullableMap.filter((entry) => entry.value > 2);
213+
// ignore: avoid_print
214+
print(filteredMap); // {a: 11, c: 3}
215+
216+
// Map keys and values
217+
final mappedMap = nullableMap.mapKeysAndValues(
218+
(entry) => MapEntry(entry.key.toUpperCase(), entry.value.toString()),
219+
);
220+
// ignore: avoid_print
221+
print(mappedMap); // {A: 11, B: 2, C: 3}
222+
223+
// Iterate through the map
224+
// ignore: avoid_print
225+
nullableMap.forEachEntry((key, value) => print('$key: $value'));
226+
// Output:
227+
// a: 11
228+
// b: 2
229+
// c: 3
230+
231+
// Check if the map contains a key or value
232+
// ignore: avoid_print
233+
print(nullableMap.containsKeyOrNull('a')); // true
234+
// ignore: avoid_print
235+
print(nullableMap.containsValueOrNull(4)); // false
236+
237+
// Return a default value if the Future completes with null
238+
final int result2 = await nullableFuture.orDefault(5);
239+
// ignore: avoid_print
240+
print(result2); // 42
241+
242+
// Return null if the Future completes with an error
243+
final int? errorHandled = await failedFuture.onErrorReturnNull();
244+
// ignore: avoid_print
245+
print(errorHandled); // null
246+
247+
// Return a default value if the Future completes with an error
248+
final int? errorHandledWithValue = await failedFuture.onErrorReturn(5);
249+
// ignore: avoid_print
250+
print(errorHandledWithValue); // 5
251+
252+
// Provide an alternative Future if the original completes with null
253+
// ignore: unnecessary_nullable_for_final_variable_declarations
254+
final Future<int?>? nullableFutureWithNull = Future.value();
255+
final int alternative = await nullableFutureWithNull.orElse(() async => 99);
256+
// ignore: avoid_print
257+
print(alternative); // 99
258+
259+
// Execute an action when the Future completes
260+
// ignore: avoid_print
261+
await nullableFuture.whenComplete(() => print('Completed')); // Completed
262+
263+
// Ignore any errors the Future may throw
264+
await failedFuture.ignoreErrors(); // No output, error ignored
265+
266+
// Timeout a Future and return null if it doesn't complete in time
267+
// ignore: unnecessary_nullable_for_final_variable_declarations
268+
final Future<int?>? slowFuture =
269+
Future.delayed(const Duration(seconds: 2), () => 10);
270+
final int? timedOut =
271+
await slowFuture.timeoutWithNull(const Duration(seconds: 1));
272+
// ignore: avoid_print
273+
print(timedOut); // null
274+
275+
// Chain another Future using thenOrNull
276+
final Future<String?> chained =
277+
nullableFuture.thenOrNull((value) => Future.value('Value: $value'));
278+
// ignore: avoid_print
279+
print(await chained); // Value: 42
280+
281+
// Catch an error and return null using catchErrorOrNull
282+
final int? caughtError = await failedFuture.catchErrorOrNull((error) {
283+
// ignore: avoid_print
284+
print('Caught error: $error');
285+
});
286+
// ignore: avoid_print
287+
print(caughtError); // Caught error: Exception: Failed, null
288+
186289
// Throws a [NotImplementedError] indicating that an operation is
187290
try {
188291
todo();

lib/nullx.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@ library nullx;
55

66
export 'src/collections.dart';
77
export 'src/exception.dart';
8+
export 'src/future.dart';
9+
export 'src/map.dart';
810
export 'src/types.dart';
911
export 'src/utils.dart';

lib/src/future.dart

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import 'dart:async';
2+
3+
/// Extension on `Future<T?>?` providing additional null-aware and error
4+
/// handling operations.
5+
///
6+
/// This extension adds methods to nullable `Future<T?>` objects, allowing for
7+
/// more expressive handling of asynchronous operations that could result in
8+
/// null or error states. It includes methods for providing default values,
9+
/// handling errors gracefully, and more nuanced manipulations like executing
10+
/// alternative futures or suppressing errors.
11+
extension NullableFutureExtensions<T> on Future<T?>? {
12+
/// Returns the future's value if not null; otherwise, returns a default value
13+
Future<T> orDefault(T defaultValue) async => (await this) ?? defaultValue;
14+
15+
/// Returns the future's value, or null if the future itself is null.
16+
Future<T?> orNull() async => await this;
17+
18+
/// Attempts to return the future's value; on error, returns a specified value
19+
Future<T?> onErrorReturn(T value) async {
20+
try {
21+
return await this;
22+
} catch (e) {
23+
return value;
24+
}
25+
}
26+
27+
/// Attempts to return the future's value; on error, returns null.
28+
Future<T?> onErrorReturnNull() async {
29+
try {
30+
return await this;
31+
} catch (e) {
32+
return null;
33+
}
34+
}
35+
36+
/// Returns the future's value if not null; otherwise, executes an alternative
37+
/// future.
38+
Future<T> orElse(Future<T> Function() alternative) async {
39+
return (await this) ?? await alternative();
40+
}
41+
42+
/// Executes a specified action when the future completes, regardless of the
43+
/// outcome.
44+
Future<T?> whenComplete(Function() action) async {
45+
try {
46+
return await this;
47+
} finally {
48+
action();
49+
}
50+
}
51+
52+
/// Suppresses any errors that occur during the future's execution.
53+
Future<void> ignoreErrors() async {
54+
try {
55+
await this;
56+
} catch (_) {}
57+
}
58+
59+
/// Returns null if the future does not complete within a specified duration.
60+
Future<T?> timeoutWithNull(Duration duration) async {
61+
try {
62+
return await this?.timeout(duration);
63+
} catch (e) {
64+
return null;
65+
}
66+
}
67+
68+
/// Applies a transformation to the future's value if not null.
69+
Future<R?> thenOrNull<R>(FutureOr<R?> Function(T?) onValue) async {
70+
return this == null ? null : await this!.then(onValue);
71+
}
72+
73+
/// Attempts to return the future's value; on error, executes an onError
74+
/// function and returns null.
75+
Future<T?> catchErrorOrNull(
76+
Function(Object) onError, {
77+
bool Function(Object)? test,
78+
}) async {
79+
try {
80+
return await this;
81+
} catch (e) {
82+
if (test == null || test(e)) {
83+
onError(e);
84+
return null;
85+
}
86+
rethrow;
87+
}
88+
}
89+
}

lib/src/map.dart

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/// Extension on `Map<K, V>?` providing additional null-aware operations.
2+
///
3+
/// This extension adds methods to `Map` objects that allow for more expressive
4+
/// handling of operations that could involve `null` maps. It includes methods
5+
/// for checking if a map is null or empty, providing default values, and more
6+
/// nuanced manipulations like filtering, updating, and transforming maps safely
7+
extension NullableMapExtensions<K, V> on Map<K, V>? {
8+
/// Checks if the map is null or empty.
9+
bool get isNullOrEmpty => this == null || this!.isEmpty;
10+
11+
/// Checks if the map is not null and not empty.
12+
bool get isNotNullOrEmpty => !isNullOrEmpty;
13+
14+
/// Returns the map if it's not null, otherwise returns the provided default
15+
/// value.
16+
Map<K, V> orDefault(Map<K, V> defaultValue) => this ?? defaultValue;
17+
18+
/// Returns the value for the given key if it exists, otherwise returns the
19+
/// provided default value.
20+
V getOrElse(K key, V defaultValue) => this?[key] ?? defaultValue;
21+
22+
/// Puts the default value for the given key if the key is absent in the map.
23+
void putIfAbsentOrElse(K key, V defaultValue) {
24+
if (this != null) {
25+
this!.putIfAbsent(key, () => defaultValue);
26+
}
27+
}
28+
29+
/// Updates the value for the given key using the provided update function.
30+
void updateValue(K key, V Function(V?) update) {
31+
if (this != null) {
32+
this![key] = update(this![key]);
33+
}
34+
}
35+
36+
/// Returns a new map containing the entries that satisfy the provided test.
37+
Map<K, V> filter(bool Function(MapEntry<K, V>) test) {
38+
if (this == null) return {};
39+
return Map<K, V>.fromEntries(this!.entries.where(test));
40+
}
41+
42+
/// Maps the keys and values of the map using the provided convert function
43+
/// and returns a new map.
44+
Map<RK, RV> mapKeysAndValues<RK, RV>(
45+
MapEntry<RK, RV> Function(MapEntry<K, V>) convert,
46+
) {
47+
if (this == null) return {};
48+
return Map<RK, RV>.fromEntries(this!.entries.map(convert));
49+
}
50+
51+
/// Performs an action for each key-value pair in the map.
52+
void forEachEntry(void Function(K key, V value) action) {
53+
if (this != null) {
54+
this!.forEach(action);
55+
}
56+
}
57+
58+
/// Checks if a key exists in the map, safely handling null maps.
59+
bool containsKeyOrNull(K key) => this?.containsKey(key) ?? false;
60+
61+
/// Checks if a value exists in the map, safely handling null maps.
62+
bool containsValueOrNull(V value) => this?.containsValue(value) ?? false;
63+
}

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: nullx
22
homepage: https://shtanko.dev
33
description: >-
44
nullx is a collection of elegant extensions for handling null types in Dart.
5-
version: 0.1.6
5+
version: 0.1.7
66
repository: https://github.com/ashtanko/nullx
77

88
topics:

0 commit comments

Comments
 (0)