Dart: Filtering Streams

Filtering a stream means picking only some events from it based on rules you set. Instead of using every value that comes, you choose which ones to keep. This helps you work with just the data you want, making your program cleaner and easier to control. For example, you can filter to get only even numbers or only certain words from a stream.

Using .where() to Filter Streams

The .where() method lets you choose only the values from a stream that match a condition. It returns a new stream with just the matching values.

void main() {

  Stream<int>.fromIterable([1, 2, 3, 4, 5])
    .where((n) => n.isEven)
    .listen((value) => print('Even: $value'));

}

In this example, we start with a stream of numbers from 1 to 5. The .where() method checks if each number is even using n.isEven. Only the even numbers (2 and 4) pass the test. The program prints each one with the label “Even”.

Using .firstWhere() to Get First Match

The .firstWhere() method finds the first item in a stream that matches a rule you give. It stops looking after finding the first match.

void main() {

  Stream<int>.fromIterable([1, 2, 3, 4, 5])
    .firstWhere((n) => n > 3)
    .then((num) => print('First > 3: $num'));

}

In this example, we have a stream of numbers from 1 to 5. We use .firstWhere() to find the first number that is greater than 3. The number 4 matches the rule, so the program prints “First > 3: 4”.

Using .lastWhere() to Get Last Match

The .lastWhere() method finds the last item in a stream that matches a rule. It checks every value until the end, then gives you the last one that fits.

void main() {

  Stream<int>.fromIterable([1, 2, 3, 4, 5])
    .lastWhere((n) => n.isEven)
    .then((num) => print('Last even: $num'));

}

In this example, we have a stream of numbers from 1 to 5. We use .lastWhere() to find the last even number. It finds 2 and 4, but 4 is the last one. So the program prints: “Last even: 4”.

Using .singleWhere() to Get Single Match

The .singleWhere() method returns the only item in the stream that matches a rule. If more than one item matches, or if no item matches, it will cause an error because it expects exactly one match.

Example: Multiple Matches (Will Cause an Error)

This example tries to find a number in the stream, but there is more than one match.
.singleWhere() expects only one, so this will cause an error.

void main() {

  Stream<int>.fromIterable([1, 3, 3])
    .singleWhere((n) => n == 3)
    .then((num) => print('Single 3: $num'));

}

In this example, the stream has the numbers 1, 3, and 3. We ask .singleWhere() to find the single number equal to 3. Since there are two 3s, this will cause an error because .singleWhere() expects exactly one match.

Example: Handling the Error Gracefully

This is the same as the previous example, but we use .catchError() to handle the error.
This stops the program from crashing if there are too many matches (or none at all).

void main() {

  Stream<int>.fromIterable([1, 3, 3])
    .singleWhere((n) => n == 3)
    .then((num) => print('Single 3: $num'))
    .catchError((e) => print('Error: $e'));

}

This is the same example as above, but now we catch the error using .catchError(). This way, if the stream has more than one match (or none at all), we can handle it properly instead of the app crashing.

Example: Single Match (Works Fine)

Here, the stream has only one item that matches.
This is the correct way to use .singleWhere() — it works without any error.

void main() {

  Stream<int>.fromIterable([1, 2, 3])
    .singleWhere((n) => n == 2)
    .then((num) => print('Single 2: $num'))
    .catchError((e) => print('Error: $e'));

}

In this example, the stream has 1, 2, and 3. We look for a single number equal to 2. Since there is exactly one 2, it matches successfully and prints the result.

So remember: use .singleWhere() only when you’re sure the condition matches exactly one item, and always include error handling just in case.

Using .take() and .skip() to Limit Events

The .take() method lets you keep only the first N events from a stream. The .skip() method ignores the first N events and starts from the rest.

void main() {

  Stream<int>.fromIterable([10, 20, 30, 40])
    .skip(1)
    .take(2)
    .listen((num) => print('Filtered: $num'));

}

In this example, the stream has four numbers: 10, 20, 30, and 40. First, .skip(1) ignores the number 10. Then, .take(2) picks the next two numbers, 20 and 30. The program prints these two numbers as filtered results.

Combining .where() and .take()

You can use .where() to filter stream events and then .take() to limit how many filtered events you get.

void main() {

  Stream<int>.fromIterable([1, 2, 3, 4, 5, 6])
    .where((n) => n.isEven)
    .take(2)
    .listen((num) => print('Even taken: $num'));

}

In this example, the stream has numbers 1 to 6. The .where() keeps only even numbers (2, 4, 6). Then .take(2) picks the first two even numbers, 2 and 4. The program prints these numbers as the filtered and limited results.

.skipWhile() and .takeWhile() for Conditional Filtering

You can use .skipWhile() to ignore events as long as a condition is true, and .takeWhile() to keep events only while a condition is true.

void main() {

  Stream<int>.fromIterable([1, 2, 3, 4, 5, 6, 7])
    .skipWhile((n) => n < 3)
    .takeWhile((n) => n < 6)
    .listen((num) => print('Filtered: $num'));

}

In this example, the stream has numbers 1 to 7. .skipWhile((n) => n < 3) skips 1 and 2 because they are less than 3. Then .takeWhile((n) => n < 6) keeps numbers 3, 4, and 5 because they are less than 6. The program prints these filtered numbers as output.

Using .distinct() to Remove Duplicates

The .distinct() method removes repeated events from the stream. It only lets unique values pass through.

void main() {

  Stream<int>.fromIterable([1, 2, 2, 3, 3, 4])
    .distinct()
    .listen((num) => print('Unique: $num'));

}

In this example, the stream has some numbers repeated like 2 and 3. Using .distinct(), the stream only sends each unique number once. The output shows the unique numbers without duplicates.

Filtering Strings by Length

You can filter strings in a stream by checking their length. This helps pick only words that are longer or shorter than a certain number of letters.

void main() {

  Stream<String>.fromIterable(['cat', 'elephant', 'dog', 'giraffe'])
    .where((word) => word.length > 4)
    .listen((word) => print('Long word: $word'));

}

In this example, the stream has different animal names. Using .where(), it filters only the words longer than 4 letters like “elephant” and “giraffe”. These words get printed as “Long word: …”

Fun Example: Filter Emoji Stars

This example shows how to keep only the star emojis () from a stream of different emojis.

void main() {

  Stream<String>.fromIterable(['⭐', '🌙', '⭐', '🔥'])
    .where((emoji) => emoji == '⭐')
    .listen((star) => print('Star: $star'));

}

Here, the stream has a mix of emojis. Using .where(), the code filters only the stars and prints each one as “Star: ⭐”. It’s a simple way to pick just what you want from a stream.

Conclusion

Filtering streams means picking events that match your rules. You can use .where() to keep many events, or .firstWhere(), .lastWhere(), and .singleWhere() to get specific ones. Methods like .take(), .skip(), and .distinct() help you limit or clean the stream. Mixing these tools lets you shape streams just how you want. Try them out to make your stream work fun and easy.