Skip to content

Commit

Permalink
Add nullable stream and object extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
ashtanko committed Nov 1, 2024
1 parent 1e0c279 commit fac94b2
Show file tree
Hide file tree
Showing 7 changed files with 429 additions and 1 deletion.
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.13
- Add nullable stream extensions
- Add nullable object extensions

## 0.1.12

- Remove deprecated methods
Expand Down
2 changes: 2 additions & 0 deletions lib/nullx.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ export 'src/either.dart';
export 'src/exception.dart';
export 'src/future.dart';
export 'src/map.dart';
export 'src/object.dart';
export 'src/stream.dart';
export 'src/types.dart';
export 'src/utils.dart';
50 changes: 50 additions & 0 deletions lib/src/object.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
extension NullableExtensions on Object? {
/// Checks if the object is null.
///
/// Returns `true` if the object is null, `false` otherwise.
bool get isNull => this == null;

/// Checks if the object is not null.
///
/// Returns `true` if the object is not null, `false` otherwise.
bool get isNotNull => this != null;

/// Allows chaining a function if the object is not null.
///
/// [operation] The function to apply to the object if it is not null.
/// Returns the result of the function or `null` if the object is null.
T? mapIfNotNull<T>(T Function(Object it) operation) {
if (this != null) {
return operation(this!);
}
return null;
}

/// Allows performing an action if the object is not null.
///
/// [action] The function to execute if the object is not null.
void run(void Function(Object it) action) {
if (this != null) {
action(this!);
}
}

/// Returns the object if it is not null, otherwise returns a default value.
///
/// [defaultValue] The value to return if the object is null.
/// Returns the object or the default value.
T orDefault<T>(T defaultValue) {
if (this != null) {
return this as T;
}
return defaultValue;
}

/// Maps the object to another type if it is not null.
///
/// [transform] The function to apply to the object if it is not null.
/// Returns the transformed object or `null` if the object is null.
T? map<T>(T Function(Object it) transform) {
return this != null ? transform(this!) : null;
}
}
112 changes: 112 additions & 0 deletions lib/src/stream.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import 'package:async/async.dart' show StreamGroup, StreamZip;

extension NullableStreamExtensions<T> on Stream<T>? {
/// Returns the stream if it is not null, otherwise returns a stream with the
/// default value.
///
/// [defaultValue] The value to emit if the stream is null.
Stream<T> orDefault(T defaultValue) {
return this ?? Stream.value(defaultValue);
}

/// Maps the stream using the provided transform function if the stream is
/// not null.
///
/// [transform] The function to apply to each element of the stream.
/// Returns the transformed stream or null if the original stream is null.
Stream<R>? mapIfNotNull<R>(R Function(T) transform) {
return this?.map(transform);
}

/// Listens to the stream if it is not null.
///
/// [onData] The function to handle data events from the stream.
void listenIfNotNull(void Function(T) onData) {
this?.listen(onData);
}

/// Combines multiple streams into a single stream of lists of values.
///
/// [streams] The list of streams to combine.
/// Returns a stream of lists of values from the combined streams.
static Stream<List<T>> combineStreams<T>(List<Stream<T>?> streams) {
final nonNullStreams = streams.whereType<Stream<T>>().toList();
if (nonNullStreams.isEmpty) return Stream.value([]);
return StreamZip(nonNullStreams).asBroadcastStream();
}

/// Unwraps the stream, throwing an error if the stream or any value in the
/// stream is null.
///
/// Returns the unwrapped stream.
Stream<T> unwrap() {
if (this == null) {
return Stream.error('Stream is null');
}
return this!.map((value) {
if (value == null) {
throw 'Value is null';
}
return value;
});
}

/// Executes the provided action if the stream is empty or null.
///
/// [action] The function to execute if the stream is empty or null.
void onEmpty(void Function() action) {
if (this == null) {
action();
} else {
this!.isEmpty.then((isEmpty) {
if (isEmpty) action();
});
}
}

/// Converts the stream to a broadcast stream or returns an empty stream if
/// the original stream is null.
///
/// Returns the broadcast stream or an empty stream.
Stream<T> toBroadcastStreamOrEmpty() {
return this?.asBroadcastStream() ?? const Stream.empty();
}

/// Returns the first element of the stream or the default value if the stream
/// is null or empty.
///
/// [defaultValue] The value to return if the stream is null or empty.
/// Returns the first element of the stream or the default value.
Future<T> firstOrDefault(T defaultValue) async {
if (this == null) return defaultValue;
try {
return await this!.first;
} catch (_) {
return defaultValue;
}
}

/// Merges the stream with another stream.
///
/// [otherStream] The stream to merge with.
/// Returns the merged stream.
Stream<T> mergeWith(Stream<T> otherStream) {
if (this == null) return otherStream;
return StreamGroup.merge([this!, otherStream]);
}

/// Executes the provided action if the stream is null.
///
/// [action] The function to execute if the stream is null.
void handleNull(void Function() action) {
if (this == null) action();
}

/// Filters the stream using the provided predicate if the stream is not null.
///
/// [predicate] The function to test each element of the stream.
/// Returns the filtered stream or null if the original stream is null.
Stream<T>? filterIfNotNull(bool Function(T) predicate) {
return this?.where(predicate);
}
}
3 changes: 2 additions & 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.12
version: 0.1.13
repository: https://github.com/ashtanko/nullx

topics:
Expand All @@ -23,6 +23,7 @@ environment:
sdk: '>=2.12.0 <4.0.0'

dependencies:
async: ^2.10.0
dartz: ^0.10.1

dev_dependencies:
Expand Down
82 changes: 82 additions & 0 deletions test/object_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import 'package:nullx/nullx.dart';
import 'package:test/test.dart';

void main() {
group('NullableExtensions', () {
test('isNull should return true if the object is null', () {
const Object? value = null;
expect(value.isNull, isTrue);
});

test('isNull should return false if the object is not null', () {
// ignore: unnecessary_nullable_for_final_variable_declarations
const Object? value = 'Hello';
expect(value.isNull, isFalse);
});

test('isNotNull should return true if the object is not null', () {
// ignore: unnecessary_nullable_for_final_variable_declarations
const Object? value = 'Hello';
expect(value.isNotNull, isTrue);
});

test('isNotNull should return false if the object is null', () {
const Object? value = null;
expect(value.isNotNull, isFalse);
});

test('let should perform operation if object is not null', () {
// ignore: unnecessary_nullable_for_final_variable_declarations
const Object? value = 'Hello';
final result = value.mapIfNotNull((it) => (it as String).length);
expect(result, 5);
});

test('let should return null if object is null', () {
const Object? value = null;
final result = value.mapIfNotNull((it) => (it as String).length);
expect(result, isNull);
});

test('run should execute action if object is not null', () {
// ignore: unnecessary_nullable_for_final_variable_declarations
const Object? value = 'World';
String? result;
value.run((it) => result = 'Hello, ${it as String}');
expect(result, 'Hello, World');
});

test('run should not execute action if object is null', () {
const Object? value = null;
String? result = 'Hello';
value.run((it) => result = 'World');
expect(result, 'Hello'); // Result should be unchanged
});

test('orDefault should return default if object is null', () {
const Object? value = null;
final result = value.orDefault('Default');
expect(result, 'Default');
});

test('orDefault should return object if it is not null', () {
// ignore: unnecessary_nullable_for_final_variable_declarations
const Object? value = 'Existing';
final result = value.orDefault('Default');
expect(result, 'Existing');
});

test('map should transform object if it is not null', () {
// ignore: unnecessary_nullable_for_final_variable_declarations
const Object? value = 'Flutter';
final result = value.map((it) => (it as String).length);
expect(result, 7);
});

test('map should return null if object is null', () {
const Object? value = null;
final result = value.map((it) => (it as String).length);
expect(result, isNull);
});
});
}
Loading

0 comments on commit fac94b2

Please sign in to comment.