BLoC Architecture with Flutter
1. What is the BLoC architecture?
The BLoC architecture (Business Logic Component) allows us a parting between business logic from our application and from the UI through streams use. We built all business logic with those stream, which allows us the use of Reactive libraries like RxDart.
2. What are streams?
Resuming, its a source of asynchronous events.
The streams concept its kinda complicated, so, lets make use of a analogy.
Let’s imagine a cheese factory. Inside our factory, what we will produce? That’s right, cheese, however, our factory is a little bit special, when we said that it was a factory that only made cheese, it is because it only produces cheese.
Our factory doesn’t have any remote office and if the person wants to make an order, it’ll have to go directly to the company and write out a form requiring a cheese, from cheese type, only. So, our customer fill the form up, now, he’ll have to hand over to the form supervisor.
And what is the form supervisor job? Receive the form that has been written and take it to the factory’s interior to begin the production’s process. Now that the request has been delivered to the supervisor, he’ll pass it to the factory’s orders inspector.
As soon the factory receives the order, it will be analyzed by the inspector. He will collect the type of cheese that has been solicited. The inspector doesn’t care with anything else, neither the cheese’s size, neither for who wants it is, nothing, ONLY if it is a cheese. Once the inspector has the type, he will re-pass to the cooker.
Our cooker will analyze and will say it: “Hmm, it is a cheese? If it is ‘yes’, I’ll do it, otherwise, I won’t do it and I will send a note telling that the type he ordered it is incorrect and will be shipped to the collect point.” It doesn’t matter what happens, our customer will have to come to the factory to pick it up his cheese. Yes, our factory doesn’t make deliveries.
In case the order had been made it correctly or not, the request will be take and send it to, what I’ll call it: “Collect point”. Next, the customer job have to go to collect point and see if your cheese its ready or not. If, for some reason the cheese has not been made, its because he filled the form with an incorrect type of food. In that case, if he wants his cheese, he will have to remake your order (request), with the correct type and wait for the response at the collect point.
Our cheese factory features:
1.1 Our factory receives an order, process it, send it to another point, in that case, to the order supervisor;
1.2 The factory is not built as soon a customer arrives. It is already built;
1.3 The factory spends a lot of time waiting an order to arrive it. As soon that the order it is made, the factory doesn’t disappear right after it, it will be re-utilized for futures requests;
1.4 Someone has to be around waiting for the order to arrive. That’s our entry point to the factory’s interior;
1.5 After the cheese it is made, someone has to pick it up. The cheese will be left it out at the collect point, in case the cheese had been made, the customer will be free to take his order to his home. Although, it will be left an error message requiring that the customer come back and made another order, now with the correct type of food.
Now, lets model our cheese factory using Dart.
Moving on, we need to declare our main function and start write our code:
The StreamController has two additional objects already built-in: sink (orders supervisor) and stream (orders inspector).
Sink: It’s the ability to add new data to be processed by our stream. Comparing to our analogy, it is our order supervisor. The order is not directly ship it to the stream, first, the sink will receive it and then, the same sink will hand it to the stream.
Stream: It’s the function which will have to take the received order and leave it to the factory’s cooker.
OK, now that our orders supervisor already brought the form to the inspector, lets reference it our stream so we can make the order to be re-passed to the cooker:
Now we just need to model our cooker and create a function so with that, he can prepare the solicited cheese to the customer:
Our orders inspector, to be more specific, it’s the StreamController ‘sink.add()’’s function. It will receive new types of data that will be taken and processed by the stream that we just created.
The stream receives and process this request. The stream’s map function look to the received value, process it, return the order type and then, hand it to the cooker.
The cooker, a.k.a ‘StreamTransformer’, observes the kind of type that has been sent it, process, prepare and then send it back to the stream.
The big difference between the ‘map’ and ‘StreamTransformer’ functions, is that in the ‘map’ function we have a 1:1 rate in terms of orders that can be received and returned to the stream. While with the ‘StreamTransformer’ function, we receive a type of cheese, however, we can product more. The ‘sink.add()’ function is more flexible about that.
This ‘sink’ in the ‘controller.sink.add()’ serves to initiate our process. While the sink presents in the ‘handleData’ function adds a value right above the line 47, where is at our cooker works. So, if we call the ‘sink.add()’ function inside our transformer, it doesn’t starts the process, only adds a new value to the cooker.
In the end, everything goes to the collect point (listen function), it can be the cheese or an error message from the cooker (transformer).
Our collect point returns a message informing that your cheese it’s ready. The ‘onError’ function it’s called when it show some kinda of error during the process, in our case, a string that is different from the cheese value.
In practical terms, the architecture can be compared to the ‘factory method’ design standard.