The basic of stream in Dart

Going through the fundamentals of stream api in dart programming language.


Baraa Abuzaid
@baraaabuzaid
The basic of stream in Dart

If you’ve been toying around with Dart and Flutter you would’ve noticed how streams are very much well integrated into the systems. streams are a very powerful concept that paves the way into building very solid abstractions, that’ll probably let you reason about your problem domain elegantly.
So what streams in Dart? It is basically a sequence of asynchronous events that may or may not be ready at the time you asked for it But, rather tells you when it’s ready. In general, it is the Dart way on doing Async programming and if you are coming for java’s world it’s very similar to Java 8 streams API. Meanwhile, in Dart there is also another way of doing Async. Which is Future and it is also very much the same as promise in javascript. So if you’re Java/Js developer switching to Dart will feel very much at home.

In the beginning, it gets very easy to confuse between the two (stream/future). but, there are settle differences between them. In this article will discuss the stream in Dart and leave (Dart Future) to the subsequent one. So to make it clear let’s look at a simple example. Suppose we would like to compute the average for a list of integers. Doing so by using stream will be like.

 main() async{

    List<int> set1 = [10,12,13,30];


    Stream<int> streamOfNumbers = computeSum(set1);
    
    int result=0;
    await for(int value in streamOfNumbers){
      result +=value;
    }
    
    print(result);// result : 65

  }
  
  Stream<int> computeSum(List<int> lst) async*{
    for(int x in lst){
      yield x;
    }
  }

Here you may notice that we are looping through a list of values with normal for-loop syntax but instead of using return value we use yield. yield allow us to pass the result back to the stream as they are becoming available. Basically returning the result in chunks.
In addition, the function must be declared as an asynchronous generator with the help of (async*) modifier.

Moreover, in the main function to process the stream, we are waiting for it as it becomes available, using (await for-loop). Furthermore, (await for-loop)  consumes the stream allowing us to loop through its values as if it is a normal list. But there is gotcha here…
when you are using await to consume a stream or any async operations we must also declare the function as an asynchronous function by using the (async) modifiers. As we are already done in the main function. And that’ll lead us to our next point.

What are the Generators in Dart? and async* vs sync* ?

The generator is a function the return a sequence. if it is asynchronous we use async* otherwise sync*. However, in both cases, the return value from the generator is evaluated lazily. using what’s called lazy evaluation. In other words, the evaluation of the sequence will differ until it’s been consumed. In the case of the asynchronous generator. The generator will be responsible for pushing the value to the consumer on its own pace.
The following table might help you to remember the different types that we discuss so far.

Type
Modifier
Return Type
synchronous function
none
any
asynchronous function
async
Futur
synchronous generator function
sync*
Iterable
asynchronous generator function
async*
Stream

 

Now, let’s do the same things by throwing some functional programming into the mix

 main(){

    List<int> set1 = [10,12,13,30];
    computeSum(set1)
        .reduce((accumlator,value)=> accumlator += value)
        .asStream().forEach(print); // result : 65
  }
  
  Stream<int> computeSum(List<num> lst) async*{
    for(num x in lst){
      yield x;
    }
  }

Basically, we are doing the same thing, but instead of using for-loop, here we are using reduce() which is a functional programming feature that made available to us when using Dart streams. reduce() execute a callback on each item on the stream with two params accumulator and value. The former has a hold of our previous return value and the latter has the current value in the stream. And because reduce consume our original stream, we need to push each return value from our reduce() function into a new stream using asStream()  and pass it again to another function that is forEach() to print out the result.

 

 

Photo by Alexandra Cozmei

Show Comments (1)

Comments

  • Bedirhan

    your introduction of the article explaining the concept is good. Also, streams can be paused which is a powerful feature. You can pause and resume whenever you feel like without interrupting or breaking the code.

    • Article Author
    • Reply