Once upon a time there lived a man John who was very good at making pizzas. He made many different varieties of pizzas like Neapolitan pizza, California style pizza, Sushi pizza, Marinara pizza etc. Pizzas made by John were so yummy that people from all over the world visited him to taste the magic that he spelled within the pizza he made. John had a small house where he took orders, baked pizzas and delivered to the costumers. He had one young beautiful daughter Mia(10% of customers visited the house to see her 😛. Just kidding) who helped her father in his business. John followed a very simple yet effective process to make good business:

Mia took orders from the customers and passed it over to her John. John will look at the order and check if the pizza can be baked or not(no ingredients). If he had the ingredients he will bake the pizza and pass the order to collect office. Costumers will come to collect office and take their order .If John can’t bake the pizza he will tell put a note for the customer saying that “The pizza you ordered is not available”.

Takeaway from the Story

After going through that interesting story. There are few interesting things that I would like to point out which will help us to understand the complete concept of Streams.

The house where pizzas are baked. That house is a permanent place or it won’t be created every time when a new customer shows up. The house is created only once and it will take orders and process it. Mia’s only responsibility is to take order and pass it to John. She won’t be deciding whether that pizza can be baked or not. So Mia will only pass the order to John to process it. John is the decision maker. He will process the order. He will decide if the pizza can be baked or not. Once he is done processing. He will pass the output(pizza or “Not available) to collect office. Customers will come and collect their order(pizza or “Not available”) from the collect office.

So in summary there will be a sequential flow of data into the stream and those data in the stream will be processed and sent to output stream. Let’s now convert the above story into a working code. While in that process I will explain you the complete picture of this Stream concept.

Streams in Dart

When we talk about Streams specific to dart. We will be using the async library provided by dart. This library supports asynchronous programming and contains all the classes and methods to create Streams. So without any further discussion let’s start coding our pizza house. Just a min, before implementing anything let me first show you the final product. So that you can connect things while I put down the code here and it will be easy for you to understand my explanations for each line of code.

If you watch the video. There is a menu which consist of 4 Buttons. Each button has a pizza name written on it. If any of the button is clicked, it will place an order of that particular type of pizza. If you click the button the order is taken by Mia and passed it over to John. John will process it and will put the output in the collect office. Customer will come to collect office and collect his order. He might get the ordered pizza(Image and the success message) or “Out of Stock” message if the pizza is not available. Customers can order same pizza multiple times. But if the stock is over for a particular pizza the output will be “Out of Stock”.

I hope you got some idea about the implementation 😄. If not I will give my best to explain you the complete implementation in detail.

Let’s code

Create a new Flutter project using. If you don’t know how check this. Remove all the code from main.dart file and paste the below code:

import 'package:flutter/material.dart';

import 'src/app.dart';



void main() => runApp(new MyApp());

3. You must be seeing an error by now at MyApp() . Don’t sweat it, we will be fixing it in the next step.

4. Create a package named as src under the lib package. Now create a dart file app.dart under the src package. Paste the below code inside app.dart file.

import 'package:flutter/material.dart';

import 'pizza_house.dart';

import 'blocs/provider.dart';



class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return Provider(

child: MaterialApp(

home: new PizzaHouse(),

),

);

}

}

As I said in my prerequisite section that you should be aware with BLoC pattern. If you marked yourself green there than you understand the use of Provider() here. If you not able to recap then in simple words it is an InheritedWidget which provide access to our bloc instance to the whole app. After pasting the above code you must be seeing some errors. Don’t worry, all will be cleared in the upcoming steps.

5. Create a dart file under the src package and name it as pizza_house.dart . Let’s keep it empty as of now. Once I am done explaining you the concept and plan of attack than we will write some code inside it.

6. Next, create another package named blocs under src package. Create two dart files inside the blocs package and name them as bloc.dart , provider.dart . Paste below code inside the provider.dart file and let’s keep the bloc.dart file empty:

import 'package:flutter/material.dart';

import 'bloc.dart';

export 'bloc.dart';



class Provider extends InheritedWidget {

final bloc = Bloc();



Provider({Key key, Widget child}) : super(key: key, child: child);



bool updateShouldNotify(_) => true;



static Bloc of(BuildContext context) {

return (context.inheritFromWidgetOfExactType(Provider) as Provider).bloc;

}

}

This class is pretty straight forward. Its sole responsible is to provide access of our bloc instance( bloc.dart ) inside our widget tree.

Just in case you wanted to see the complete project structure:

7. Now it’s time to implement the bloc.dart file. If you see the code below, do not panic. I will explain you each and every line in-depth then it will make sense I bet. So here goes the complete implementation of the bloc.dart file:

import 'dart:async';



class Bloc {



//Our pizza house

final order = StreamController<String>();



//Our collect office

Stream<String> get orderOffice => order.stream.transform(validateOrder);



//Pizza house menu and quantity

static final _pizzaList = {

"Sushi": 2,

"Neapolitan": 3,

"California-style": 4,

"Marinara": 2

};



//Different pizza images

static final _pizzaImages = {

"Sushi": "http://pngimg.com/uploads/pizza/pizza_PNG44077.png",

"Neapolitan": "http://pngimg.com/uploads/pizza/pizza_PNG44078.png",

"California-style": "http://pngimg.com/uploads/pizza/pizza_PNG44081.png",

"Marinara": "http://pngimg.com/uploads/pizza/pizza_PNG44084.png"

};





//Validate if pizza can be baked or not. This is John

final validateOrder =

StreamTransformer<String, String>.fromHandlers(handleData: (order, sink) {

if (_pizzaList[order] != null) {

//pizza is available

if (_pizzaList[order] != 0) {

//pizza can be delivered

sink.add(_pizzaImages[order]);

final quantity = _pizzaList[order];

_pizzaList[order] = quantity-1;

} else {

//out of stock

sink.addError("Out of stock");

}

} else {

//pizza is not in the menu

sink.addError("Pizza not found");

}

});



//This is Mia

void orderItem(String pizza) {

order.sink.add(pizza);

}

}

In the very first line itself you must be scratching your head 😜. What the hell is this StreamController? I know it was coming. Let me explain and ease you 😆.

StreamController

In simple words, its our Pizza house. It is responsible for taking the orders, processing it and giving out the output. But what are the methods that make StreamController a complete Pizza house or in other words what are the methods that are responsible for taking orders, processing it and giving output? Here is a small picture to answer the above question:

StreamController demystified

StreamController has two getters one is sink another is stream . sink is Mia here who will take the orders from the customer and pass it to the stream(John and then collect office). In simple words sink will add data to the stream of the StreamController. Now let’s talk about stream .It will pass the data to the outside world(collect office) after doing some processing(John). Now if you see the bloc.dart code. The below lines are the sink and stream of StreamController:

//Our pizza house

final order = StreamController<String>();



//Our collect office

Stream<String> get orderOffice => order.stream.transform(validateOrder); //This is Mia

void orderItem(String pizza) {

order.sink.add(pizza);

}

sink has a method called as add(data) which will add data to the stream . Here it is adding the order to the stream . orderOffice (collect office) is a getter which will be called in our pizza_house.dart (yet to be implemented) to give the output to the customer.

In the above code you must be wondering what is that transform() is for. It’s a method which will call John to process the incoming orders and put the processed output in the stream(collect office). Now let’s discuss about John a bit who is the main heart of the Pizza house. Here John is our validateOrder in the above code. validateOrder is a StreamTransformer.

//Validate if pizza can be baked or not. This is John

final validateOrder =

StreamTransformer<String, String>.fromHandlers(handleData: (order, sink) {

if (_pizzaList[order] != null) {

//pizza is available

if (_pizzaList[order] != 0) {

//pizza can be delivered

sink.add(_pizzaImages[order]);

final quantity = _pizzaList[order];

_pizzaList[order] = quantity-1;

} else {

//out of stock

sink.addError("Out of stock");

}

} else {

//pizza is not in the menu

sink.addError("Pizza not found");

}

});

StreamTransformer

In simple words, it will take the incoming orders from the stream and will check if the order is valid or not(pizza can be baked or not). If the order is valid it will add the output to the stream using the sink.add(successOrder) (pizza baked successfully) method. Here we are adding the image of the baked pizza in the stream to show the customer that their pizza is ready. If the order is not valid it will add it to the sink.addError(invalidOrder) telling the customer that “The pizza you order is out of stock”.

Now I hope things are getting connected for you. Before implementing the pizza_house.dart there are two more things in the bloc.dart file that need some explanation. Those are these lines of code:

//Pizza house menu and quantity

static final _pizzaList = {

"Sushi": 2,

"Neapolitan": 3,

"California-style": 4,

"Marinara": 2

};



//Different pizza images

static final _pizzaImages = {

"Sushi": "http://pngimg.com/uploads/pizza/pizza_PNG44077.png",

"Neapolitan": "http://pngimg.com/uploads/pizza/pizza_PNG44078.png",

"California-style": "http://pngimg.com/uploads/pizza/pizza_PNG44081.png",

"Marinara": "http://pngimg.com/uploads/pizza/pizza_PNG44084.png"

};

_pizzaList is our menu which will hold the type of pizzas and the total quantity that can be baked in the house. _pizzaImages is map which will hold the images of different kind of pizzas that are baked in the pizza house. These images will be shown to the customer making him feel that he has received his order 😜.

Now the final class that we will be implementing i.e pizza_house.dart file. Here is the code for it:

import 'package:flutter/material.dart';

import 'blocs/provider.dart';



class PizzaHouse extends StatelessWidget {

var pizzaName = "";

@override

Widget build(BuildContext context) {

final _bloc = Provider.of(context);

return Scaffold(

appBar: AppBar(

title: Text("Pizza House"),

),

body: Container(

child: Column(

mainAxisAlignment: MainAxisAlignment.start,

children: <Widget>[menu1(_bloc), menu2(_bloc), orderOffice(_bloc)],

),

),

);

}



menu1(Bloc bloc) {

return Container(

margin: EdgeInsets.all(8.0),

child: Row(

children: <Widget>[

Expanded(

child: RaisedButton(

child: Text("Neapolitan"),

onPressed: () {

bloc.orderItem("Neapolitan");

pizzaName = "Neapolitan";

},

),

),

Container(

margin: EdgeInsets.only(left: 2.0, right: 2.0),

),

Expanded(

child: RaisedButton(

child: Text("California-style"),

onPressed: () {

bloc.orderItem("California-style");

pizzaName = "California-style";

},

),

)

],

),

);

}



menu2(Bloc bloc) {

return Container(

margin: EdgeInsets.all(8.0),

child: Row(

children: <Widget>[

Expanded(

child: RaisedButton(

child: Text("Sushi"),

onPressed: () {

bloc.orderItem("Sushi");

pizzaName = "Sushi";

},

),

),

Container(

margin: EdgeInsets.only(left: 2.0, right: 2.0),

),

Expanded(

child: RaisedButton(

child: Text("Marinara"),

onPressed: () {

bloc.orderItem("Marinara");

pizzaName = "Marinara";

},

),

)

],

),

);

}



orderOffice(Bloc bloc) {

return StreamBuilder(

stream: bloc.orderOffice,

builder: (context, AsyncSnapshot<String> snapshot) {

if (snapshot.hasData) {

return Column(

mainAxisAlignment: MainAxisAlignment.center,

children: <Widget>[

Image.network(

snapshot.data,

fit: BoxFit.fill,

),

Container(

margin: EdgeInsets.only(top: 8.0),

),

Text("Yay! Collect your $pizzaName pizza")

],

);

} else if (snapshot.hasError) {

return Column(

children: <Widget>[

Image.network("http://megatron.co.il/en/wp-content/uploads/sites/2/2017/11/out-of-stock.jpg",

fit: BoxFit.fill),

Text(

snapshot.error,

style: TextStyle(

fontSize: 20.0,

),

),

],

);

}

return Text("No item in collect office");

},

);

}

}

The very first line inside the build() method is declaration of the bloc instance. Using this instance we can place orders and check the output inside the collect office. To place order of a particular type of pizza. We have implemented the logic of adding that particular pizza to the stream inside the onPressed() method of each button. Inside the onPressed() method we are calling the orderItem(pizza) which in turn calls the sink.add() method. To show customers what is the output for their order in the collect office we need to make our widgets work with streams. We will be using StreamBuilder for this.

StreamBuilder will listen to the stream from the orderOffice() method and if there is any data available it will return a widget based on the data coming from stream. StreamBuilder has two important parameters. 1) stream and 2) builder . stream take a method as parameter which returns a stream i.e orderOffice method from the bloc.dart file. stream of StreamBuilder will listen for any new data coming in. builder will take a method which has two parameters i.e context and snapshot . snapshot is something like a data provider. It will get data from the stream and provide it so that we can process it and return the right widget to be displayed. As you can see if there is any data inside the snapshot(we can check using hasData ) we will return an Image and a Text widget. The Image is the image of the pizza that was ordered. Every time a pizza is ordered its quantity is reduced(logic is handled inside the transformer). If there are no more pizza left. The output will be “Out of stock”.

Takeaway from the article

Throughout the app I have not used even a single StatefulWidget still I was able to change the state of the app. In other words I made my app reactive using the power of Streams. Streams are very helpful when you want to listen for changes in your data and react according to it. Hope now it makes sense why we should be making our app as reactive as possible using Streams.