Using Streams for Asynchronous Data in Dart

Streams in Dart provide a way to handle asynchronous data sequences. They allow you to work with a series of events or data over time, making them ideal for scenarios like user input, web socket events, and data from APIs. Streams can be thought of as a pipeline of data that can be listened to and processed as new data arrives.

Key Concepts

What is a Stream?

A Stream is a sequence of asynchronous events. You can listen to a stream to receive data as it becomes available, rather than waiting for all the data to be ready at once.

Types of Streams

  1. Single-Subscription Stream: A stream that can be listened to only once. It's typically used for operations where a single sequence of data is expected.
  2. Broadcast Stream: A stream that can have multiple listeners. It allows multiple subscribers to receive the same events.

Creating a Stream

You can create a stream using the Stream class, either by using a generator function or by using a stream controller.

Example of Creating a Stream

Stream<int> countStream() async* {
  for (int i = 1; i <= 5; i++) {
    await Future.delayed(Duration(seconds: 1)); // Simulating asynchronous work     
    yield i; // Yielding values to the stream   
    }
}

Listening to a Stream

To receive data from a stream, you need to listen to it. You can use the listen method to handle incoming data.

Example of Listening to a Stream

void main() {
  print('Counting...');
  
  countStream().listen((data) {
    print(data); // Output: 1, 2, 3, 4, 5 (one per second)   });
}

Using Async and Await with Streams

You can also use await for to listen to a stream asynchronously. This allows you to process events as they arrive.

Example of Using Async and Await

Future<void> main() async {
  print('Counting...');
  
  await for (var data in countStream()) {
    print(data); // Output: 1, 2, 3, 4, 5 (one per second)   }
}

Handling Errors in Streams

Streams can produce errors in addition to data. You can handle errors by providing an onError callback in the listen method.

Example of Error Handling in Streams

Stream<int> errorStream() async* {
  for (int i = 1; i <= 5; i++) {
    await Future.delayed(Duration(seconds: 1));
    if (i == 3) {
      throw Exception('Error at value 3');
    }
    yield i;
  }
}

Future<void> main() async {
  print('Counting with error handling...');
  
  errorStream().listen(
    (data) {
      print(data);
    },
    onError: (error) {
      print('Caught error: $error'); // Output: Caught error: Exception: Error at value 3    
       },
    onDone: () {
      print('Stream is done.');
    },
  );
}

Using Broadcast Streams

Broadcast streams allow multiple listeners to receive the same events. You can create a broadcast stream using the asBroadcastStream method.

Example of Broadcast Streams

StreamController<int> controller = StreamController<int>.broadcast();

void main() {
  controller.stream.listen((data) {
    print('Listener 1: $data');
  });

  controller.stream.listen((data) {
    print('Listener 2: $data');
  });

  for (int i = 1; i <= 3; i++) {
    controller.add(i); // Sending data to all listeners   
    }

  controller.close(); // Closing the stream 
  }

Conclusion

Streams are a powerful feature in Dart for handling asynchronous data sequences. By using streams, you can efficiently manage real-time data flows in your applications, whether from user interactions, network requests, or other sources. Understanding how to create, listen to, and manage streams will enhance your ability to build responsive and dynamic Dart applications.

PLAY QUIZ

What is a Stream in Dart?

A mechanism for executing synchronous code.

A sequence of asynchronous events that can be listened to as data becomes available.

A type of loop for iterating over collections.

A class that handles static data.