Skip to content

Commit a8acf0d

Browse files
authored
feat(mapNotNull, whereNotNull): add mapNotNull and whereNotNull (#548)
* added mapNotNull * add where_not_null.dart * docs * docs * exports * README.md * tests * tests * where_not_null_test.dart * rename * fix timer_test.dart * fix timer_test.dart * deps: update some dev deps to stable null safety release. * update tests * fix * sort imports * update impl
1 parent fc0083a commit a8acf0d

File tree

9 files changed

+325
-3
lines changed

9 files changed

+325
-3
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ Stream.fromIterable([1, 2, 3])
149149
- [flatMapIterable](https://pub.dev/documentation/rxdart/latest/rx/FlatMapExtension/flatMapIterable.html)
150150
- [groupBy](https://pub.dev/documentation/rxdart/latest/rx/GroupByExtension/groupBy.html)
151151
- [interval](https://pub.dev/documentation/rxdart/latest/rx/IntervalExtension/interval.html)
152+
- [mapNotNull](https://pub.dev/documentation/rxdart/latest/rx/MapNotNullExtension/mapNotNull.html)
152153
- [mapTo](https://pub.dev/documentation/rxdart/latest/rx/MapToExtension/mapTo.html)
153154
- [materialize](https://pub.dev/documentation/rxdart/latest/rx/MaterializeExtension/materialize.html)
154155
- [max](https://pub.dev/documentation/rxdart/latest/rx/MaxExtension/max.html)
@@ -175,6 +176,7 @@ Stream.fromIterable([1, 2, 3])
175176
- [throttleTime](https://pub.dev/documentation/rxdart/latest/rx/ThrottleExtensions/throttleTime.html)
176177
- [timeInterval](https://pub.dev/documentation/rxdart/latest/rx/TimeIntervalExtension/timeInterval.html)
177178
- [timestamp](https://pub.dev/documentation/rxdart/latest/rx/TimeStampExtension/timestamp.html)
179+
- [whereNotNull](https://pub.dev/documentation/rxdart/latest/rx/WhereNotNullExtension/whereNotNull.html)
178180
- [whereType](https://pub.dev/documentation/rxdart/latest/rx/WhereTypeExtension/whereType.html)
179181
- [window](https://pub.dev/documentation/rxdart/latest/rx/WindowExtensions/window.html)
180182
- [windowCount](https://pub.dev/documentation/rxdart/latest/rx/WindowExtensions/windowCount.html)

lib/rxdart.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
library rx;
22

33
export 'src/rx.dart';
4-
export 'src/utils/composite_subscription.dart';
5-
export 'src/utils/error_and_stacktrace.dart';
6-
export 'src/utils/notification.dart';
74
export 'streams.dart';
85
export 'subjects.dart';
96
export 'transformers.dart';
7+
export 'utils.dart';
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import 'dart:async';
2+
3+
class _MapNotNullSink<T, R extends Object> implements EventSink<T> {
4+
final R? Function(T) _transform;
5+
final EventSink<R> _outputSink;
6+
7+
_MapNotNullSink(this._outputSink, this._transform);
8+
9+
@override
10+
void add(T event) {
11+
final value = _transform(event);
12+
if (value != null) {
13+
_outputSink.add(value);
14+
}
15+
}
16+
17+
@override
18+
void addError(Object error, [StackTrace? stackTrace]) =>
19+
_outputSink.addError(error, stackTrace);
20+
21+
@override
22+
void close() => _outputSink.close();
23+
}
24+
25+
/// Create a Stream containing only the non-`null` results
26+
/// of applying the given [transform] function to each element of the Stream.
27+
///
28+
/// ### Example
29+
///
30+
/// Stream.fromIterable(['1', 'two', '3', 'four'])
31+
/// .transform(MapNotNullStreamTransformer(int.tryParse))
32+
/// .listen(print); // prints 1, 3
33+
///
34+
/// // equivalent to:
35+
///
36+
/// Stream.fromIterable(['1', 'two', '3', 'four'])
37+
/// .map(int.tryParse)
38+
/// .transform(WhereTypeStreamTransformer<int?, int>())
39+
/// .listen(print); // prints 1, 3
40+
class MapNotNullStreamTransformer<T, R extends Object>
41+
extends StreamTransformerBase<T, R> {
42+
/// A function that transforms each elements of the Stream.
43+
final R? Function(T) transform;
44+
45+
/// Constructs a [StreamTransformer] which emits non-`null` elements
46+
/// of applying the given [transform] function to each element of the Stream.
47+
const MapNotNullStreamTransformer(this.transform);
48+
49+
@override
50+
Stream<R> bind(Stream<T> stream) => Stream<R>.eventTransformed(
51+
stream, (sink) => _MapNotNullSink<T, R>(sink, transform));
52+
}
53+
54+
/// Extends the Stream class with the ability to convert the source Stream
55+
/// to a Stream containing only the non-`null` results
56+
/// of applying the given [transform] function to each element of this Stream.
57+
extension MapNotNullExtension<T> on Stream<T> {
58+
/// Returns a Stream containing only the non-`null` results
59+
/// of applying the given [transform] function to each element of this Stream.
60+
///
61+
/// ### Example
62+
///
63+
/// Stream.fromIterable(['1', 'two', '3', 'four'])
64+
/// .mapNotNull(int.tryParse)
65+
/// .listen(print); // prints 1, 3
66+
///
67+
/// // equivalent to:
68+
///
69+
/// Stream.fromIterable(['1', 'two', '3', 'four'])
70+
/// .map(int.tryParse)
71+
/// .whereType<int>()
72+
/// .listen(print); // prints 1, 3
73+
Stream<R> mapNotNull<R extends Object>(R? Function(T) transform) =>
74+
MapNotNullStreamTransformer<T, R>(transform).bind(this);
75+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import 'dart:async';
2+
3+
class _WhereNotNullStreamSink<T extends Object> implements EventSink<T?> {
4+
final EventSink<T> _outputSink;
5+
6+
_WhereNotNullStreamSink(this._outputSink);
7+
8+
@override
9+
void add(T? event) {
10+
if (event != null) {
11+
_outputSink.add(event);
12+
}
13+
}
14+
15+
@override
16+
void addError(Object error, [StackTrace? stackTrace]) =>
17+
_outputSink.addError(error, stackTrace);
18+
19+
@override
20+
void close() => _outputSink.close();
21+
}
22+
23+
/// Create a Stream which emits all the non-`null` elements of the Stream,
24+
/// in their original emission order.
25+
///
26+
/// ### Example
27+
///
28+
/// Stream.fromIterable(<int?>[1, 2, 3, null, 4, null])
29+
/// .transform(WhereNotNullStreamTransformer())
30+
/// .listen(print); // prints 1, 2, 3, 4
31+
///
32+
/// // equivalent to:
33+
///
34+
/// Stream.fromIterable(<int?>[1, 2, 3, null, 4, null])
35+
/// .transform(WhereTypeStreamTransformer<int?, int>())
36+
/// .listen(print); // prints 1, 2, 3, 4
37+
class WhereNotNullStreamTransformer<T extends Object>
38+
extends StreamTransformerBase<T?, T> {
39+
@override
40+
Stream<T> bind(Stream<T?> stream) => Stream<T>.eventTransformed(
41+
stream, (sink) => _WhereNotNullStreamSink<T>(sink));
42+
}
43+
44+
/// Extends the Stream class with the ability to convert the source Stream
45+
/// to a Stream which emits all the non-`null` elements
46+
/// of this Stream, in their original emission order.
47+
extension WhereNotNullExtension<T extends Object> on Stream<T?> {
48+
/// Returns a Stream which emits all the non-`null` elements
49+
/// of this Stream, in their original emission order.
50+
///
51+
/// For a `Stream<T?>`, this method is equivalent to `.whereType<T>()`.
52+
///
53+
/// ### Example
54+
///
55+
/// Stream.fromIterable(<int?>[1, 2, 3, null, 4, null])
56+
/// .whereNotNull()
57+
/// .listen(print); // prints 1, 2, 3, 4
58+
///
59+
/// // equivalent to:
60+
///
61+
/// Stream.fromIterable(<int?>[1, 2, 3, null, 4, null])
62+
/// .whereType<int>()
63+
/// .listen(print); // prints 1, 2, 3, 4
64+
Stream<T> whereNotNull() => WhereNotNullStreamTransformer<T>().bind(this);
65+
}

lib/transformers.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export 'src/transformers/flat_map.dart';
1919
export 'src/transformers/group_by.dart';
2020
export 'src/transformers/ignore_elements.dart';
2121
export 'src/transformers/interval.dart';
22+
export 'src/transformers/map_not_null.dart';
2223
export 'src/transformers/map_to.dart';
2324
export 'src/transformers/materialize.dart';
2425
export 'src/transformers/max.dart';
@@ -36,5 +37,6 @@ export 'src/transformers/take_until.dart';
3637
export 'src/transformers/take_while_inclusive.dart';
3738
export 'src/transformers/time_interval.dart';
3839
export 'src/transformers/timestamp.dart';
40+
export 'src/transformers/where_not_null.dart';
3941
export 'src/transformers/where_type.dart';
4042
export 'src/transformers/with_latest_from.dart';

lib/utils.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
library rx_utils;
2+
3+
export 'src/utils/composite_subscription.dart';
4+
export 'src/utils/error_and_stacktrace.dart';
5+
export 'src/utils/notification.dart';

test/rxdart_test.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import 'transformers/group_by_test.dart' as group_by_test;
6363
import 'transformers/ignore_elements_test.dart' as ignore_elements_test;
6464
import 'transformers/interval_test.dart' as interval_test;
6565
import 'transformers/join_test.dart' as join_test;
66+
import 'transformers/map_not_null_test.dart' as map_not_null_test;
6667
import 'transformers/map_to_test.dart' as map_to_test;
6768
import 'transformers/materialize_test.dart' as materialize_test;
6869
import 'transformers/merge_with_test.dart' as merge_with_test;
@@ -84,6 +85,7 @@ import 'transformers/take_while_inclusive_test.dart'
8485
import 'transformers/time_interval_test.dart' as time_interval_test;
8586
import 'transformers/timeout_test.dart' as timeout_test;
8687
import 'transformers/timestamp_test.dart' as timestamp_test;
88+
import 'transformers/where_not_null_test.dart' as where_not_null_test;
8789
import 'transformers/where_type_test.dart' as where_type_test;
8890
import 'transformers/with_latest_from_test.dart' as with_latest_from_test;
8991
import 'transformers/zip_with_test.dart' as zip_with_test;
@@ -126,6 +128,7 @@ void main() {
126128
ignore_elements_test.main();
127129
interval_test.main();
128130
join_test.main();
131+
map_not_null_test.main();
129132
map_to_test.main();
130133
materialize_test.main();
131134
merge_with_test.main();
@@ -146,6 +149,7 @@ void main() {
146149
timeout_test.main();
147150
timestamp_test.main();
148151
timer_test.main();
152+
where_not_null_test.main();
149153
where_type_test.main();
150154
with_latest_from_test.main();
151155
zip_with_test.main();
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import 'dart:async';
2+
3+
import 'package:rxdart/rxdart.dart';
4+
import 'package:test/test.dart';
5+
6+
void main() {
7+
test('Rx.mapNotNull', () {
8+
expect(
9+
Stream.fromIterable(['1', '2', 'invalid_num', '3', 'invalid_num', '4'])
10+
.mapNotNull(int.tryParse),
11+
emitsInOrder(<int>[1, 2, 3, 4]));
12+
13+
// 0-----1-----2-----3-----...-----8-----9-----|
14+
// 1-----null--3-----null--...-----9-----null--|
15+
// 1--3--5--7--9--|
16+
final stream = Stream.periodic(const Duration(milliseconds: 10), (i) => i)
17+
.take(10)
18+
.transform(MapNotNullStreamTransformer((i) => i.isOdd ? null : i + 1));
19+
expect(stream, emitsInOrder(<Object>[1, 3, 5, 7, 9, emitsDone]));
20+
});
21+
22+
test('Rx.mapNotNull.shouldThrowA', () {
23+
expect(
24+
Stream<bool>.error(Exception()).mapNotNull((_) => true),
25+
emitsError(isA<Exception>()),
26+
);
27+
28+
expect(
29+
Rx.concat<int>([
30+
Stream.fromIterable([1, 2]),
31+
Stream.error(Exception()),
32+
Stream.value(3),
33+
]).mapNotNull((i) => i.isEven ? i + 1 : null),
34+
emitsInOrder(<dynamic>[
35+
3,
36+
emitsError(isException),
37+
emitsDone,
38+
]),
39+
);
40+
});
41+
42+
test('Rx.mapNotNull.shouldThrowB', () {
43+
expect(
44+
Stream.fromIterable([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).mapNotNull((i) {
45+
if (i == 4) throw Exception();
46+
return i.isEven ? i + 1 : null;
47+
}),
48+
emitsInOrder(<dynamic>[
49+
3,
50+
emitsError(isException),
51+
7,
52+
9,
53+
11,
54+
emitsDone,
55+
]),
56+
);
57+
});
58+
59+
test('Rx.mapNotNull.asBroadcastStream', () {
60+
final stream = Stream.fromIterable([2, 3, 4, 5, 6])
61+
.mapNotNull<int>((i) => null)
62+
.asBroadcastStream();
63+
64+
// listen twice on same stream
65+
stream.listen(null);
66+
stream.listen(null);
67+
68+
// code should reach here
69+
expect(true, true);
70+
});
71+
72+
test('Rx.mapNotNull.singleSubscription', () {
73+
final stream = StreamController<int>().stream.mapNotNull((i) => i);
74+
75+
expect(stream.isBroadcast, isFalse);
76+
stream.listen(null);
77+
expect(() => stream.listen(null), throwsStateError);
78+
});
79+
80+
test('Rx.mapNotNull.pause.resume', () async {
81+
final subscription =
82+
Stream.fromIterable([2, 3, 4, 5, 6]).mapNotNull((i) => i).listen(null);
83+
84+
subscription
85+
..pause()
86+
..onData(expectAsync1((data) {
87+
expect(data, 2);
88+
subscription.cancel();
89+
}))
90+
..resume();
91+
});
92+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import 'dart:async';
2+
3+
import 'package:rxdart/rxdart.dart';
4+
import 'package:test/test.dart';
5+
6+
void main() {
7+
test('Rx.whereNotNull', () {
8+
{
9+
final notNull = Stream.fromIterable([1, 2, 3, 4]).whereNotNull();
10+
11+
expect(notNull, isA<Stream<int>>());
12+
expect(notNull, emitsInOrder(<int>[1, 2, 3, 4]));
13+
}
14+
15+
{
16+
final notNull = Stream.fromIterable([1, 2, null, 3, 4, null])
17+
.transform(WhereNotNullStreamTransformer());
18+
19+
expect(notNull, isA<Stream<int>>());
20+
expect(notNull, emitsInOrder(<int>[1, 2, 3, 4]));
21+
}
22+
});
23+
24+
test('Rx.whereNotNull.shouldThrow', () {
25+
expect(
26+
Stream<bool>.error(Exception()).whereNotNull(),
27+
emitsError(isA<Exception>()),
28+
);
29+
30+
expect(
31+
Rx.concat<int?>([
32+
Stream.fromIterable([1, 2, null]),
33+
Stream.error(Exception()),
34+
Stream.value(3),
35+
]).whereNotNull(),
36+
emitsInOrder(<dynamic>[
37+
1,
38+
2,
39+
emitsError(isException),
40+
3,
41+
emitsDone,
42+
]),
43+
);
44+
});
45+
46+
test('Rx.whereNotNull.asBroadcastStream', () {
47+
final stream =
48+
Stream.fromIterable([1, 2, null]).whereNotNull().asBroadcastStream();
49+
50+
// listen twice on same stream
51+
stream.listen(null);
52+
stream.listen(null);
53+
54+
// code should reach here
55+
expect(true, true);
56+
});
57+
58+
test('Rx.whereNotNull.singleSubscription', () {
59+
final stream = StreamController<int?>().stream.whereNotNull();
60+
61+
expect(stream.isBroadcast, isFalse);
62+
stream.listen(null);
63+
expect(() => stream.listen(null), throwsStateError);
64+
});
65+
66+
test('Rx.whereNotNull.pause.resume', () async {
67+
final subscription = Stream.fromIterable([null, 2, 3, null, 4, 5, 6])
68+
.whereNotNull()
69+
.listen(null);
70+
71+
subscription
72+
..pause()
73+
..onData(expectAsync1((data) {
74+
expect(data, 2);
75+
subscription.cancel();
76+
}))
77+
..resume();
78+
});
79+
}

0 commit comments

Comments
 (0)