Dart: Periodic Streams

A periodic stream is a special kind of stream in Dart that sends events repeatedly at fixed time intervals. Think of it as a timer that “ticks” and sends data every few seconds or milliseconds.

You use periodic streams when you want your app to do something over and over, like showing a clock, sending regular updates, or running a countdown. The stream keeps sending events until you tell it to stop.

The simple idea behind a periodic stream is that it works like a timer, creating new events at regular time steps. This makes it easy to react to time-based changes in your app.

Creating a Basic Periodic Stream

You can create a periodic stream easily with Stream.periodic(). This method needs two things: a Duration that sets how often the stream sends an event, and a callback function that generates the event data each time.

The callback receives the number of times the event has been sent so far, starting from 0. You can use this count to create useful data like a timer or counter.

For example, this code creates a stream that emits a number every second, counting up from 0. Each “tick” prints the current count:

import 'dart:async';

void main() {

  var stream = Stream.periodic(Duration(seconds: 1), (count) => count);

  stream.listen((value) => print('Tick: $value'));

}

In this example, the stream sends a number every second. The listener prints each number as it comes. This is a simple way to make a timer using Dart streams.

Customizing the Event Data

The computation callback in Stream.periodic() gives you the current tick count, starting from zero. This count lets you customize the data sent with each event, making your stream more interesting and meaningful.

For example, you can use the count to create fun messages that change over time. In the code below, the stream sends playful dog bark messages like “Bark #0”, “Bark #1”, and so on, once every second.

import 'dart:async';

void main() {

  var stream = Stream.periodic(Duration(seconds: 1), (count) => 'Bark #$count');

  stream.listen((bark) => print(bark));

}

This shows how easy it is to make a periodic stream send custom, lively events based on the tick count.

Limiting Stream Events

You can stop a periodic stream after receiving a certain number of events using the .take() method. This tells the stream to emit only the specified number of events before closing automatically.

In the example below, the stream counts down from 5 to 0, sending one number every second. The .take(6) makes sure the stream stops after emitting six events (from 5 down to 0).

import 'dart:async';

void main() {

  var stream = Stream.periodic(
    Duration(seconds: 1), 
    (count) => 5 - count
  ).take(6);

  stream.listen((value) => print('Countdown: $value'));

}

This way, you control exactly how many ticks your periodic stream sends.

Using Periodic Streams with Async Functions

You can combine periodic streams with async functions using .asyncMap(). This allows the stream to handle asynchronous operations for each event it emits.

Here’s an example where a periodic stream emits a count every 2 seconds. For each count, it calls an async function that simulates fetching a message from a server. The stream waits for the async call to complete before sending the message to the listener.

import 'dart:async';

Future<String> fetchMessage(int count) async {
  // Simulate fetching data from a server
  return 'Message #$count from server';
}

void main() {

  Stream.periodic(Duration(seconds: 2), (count) => count)
      .asyncMap(fetchMessage)
      .listen((msg) => print(msg));

}

In this code, asyncMap takes each number from the periodic stream and runs the fetchMessage async function. The listener prints each message as it arrives. This method makes it easy to work with repeated async tasks using streams.

Pausing and Resuming Periodic Streams

You can pause and resume listening to periodic streams by controlling the stream subscription. This is useful if you want to temporarily stop receiving events without canceling the subscription.

In the example below, we create a periodic stream that emits a number every second. We listen to the stream and print each tick. After 3 seconds, we pause the subscription, stopping the ticks from printing. Then after another 3 seconds, we resume listening, and the ticks continue.

import 'dart:async';

void main() {

  var stream = Stream.periodic(Duration(seconds: 1), (count) => count);
  var subscription = stream.listen((value) => print('Tick: $value'));

  Future.delayed(Duration(seconds: 3), () {
    subscription.pause();
    print('Paused');
  });

  Future.delayed(Duration(seconds: 6), () {
    subscription.resume();
    print('Resumed');
  });

}

Pausing does not cancel the stream; it just stops the events from being delivered temporarily. Resuming lets you continue where you left off. This gives you fine control over event handling in periodic streams.

Real-World Example: Clock Ticking Every Second

You can use a periodic stream to create a simple clock that ticks every second. Instead of sending a count, this stream emits the current time each time it fires. This way, you get a live update of the clock every second.

In the example, Stream.periodic is set to fire every second. It uses a callback that returns DateTime.now(), giving the exact current time at each tick. The listener prints this time, showing a ticking clock in the console.

This technique is great for building live timers, clocks, or any feature that needs regular updates based on the current time. It keeps going until you stop listening or cancel the subscription.

import 'dart:async';

void main() {

  var stream = Stream.periodic(Duration(seconds: 1), (_) => DateTime.now());
  stream.listen((time) => print('Time now: ${time.toLocal()}'));

}

This code creates a stream that sends the current time every second. Each time the stream fires, it gets the current DateTime.now() and sends it to the listener. The listener then prints the time, showing the clock ticking live in the console.

Canceling Periodic Streams

Sometimes, you want to stop receiving events from a periodic stream before it finishes on its own. You can do this by canceling the stream subscription. When you cancel, the stream stops sending new events to that listener.

import 'dart:async';

void main() {

  var stream = Stream.periodic(Duration(seconds: 1), (count) => count);
  
  StreamSubscription<int>? subscription;

  subscription = stream.listen((value) {

    print('Tick: $value');

    if (value == 4) {
      subscription?.cancel();
      print('Stream canceled');
    }

  });
  
}

This example shows how to stop a periodic stream by canceling its subscription. The stream emits a tick every second, and the listener prints each tick. When the count reaches 4 (which is the 5th tick, starting from 0), the listener calls cancel() on the subscription to stop the stream. After canceling, no more events are received, and it prints “Stream canceled.” This way, you can control when to stop listening to a periodic stream.

Conclusion

Periodic streams are a simple and powerful way to create repeated events over time in Dart. You learned how to create streams that emit values on a timer and customize the data they send, like playful messages or countdowns.

You also saw how to control these streams—limiting how many events they send, pausing and resuming them, or canceling subscriptions to stop the stream early. These controls help you manage how your app reacts to ongoing events.

Periodic streams fit naturally in many Dart applications, from building timers and clocks to handling repeated tasks like fetching data or updating UI elements regularly. They give you a clean, easy way to work with time-based events in your code.