Flutter: Widget communication with Streams
Imagine that you found the perfect way to communicate events between your Widgets ? I’m not saying, that I found the perfect one, but at least for me — imho — is one of the interesting one.
Flutter as you may know is one of the biggest new framework to build Mobile Applications that share code base which means that you code once and build for multiple systems (Android & IOS) and that versatility allow us to create new applications in almost no time.
As long you codify in flutter and absorb or understand that everything is a Widget and start to create more complex apps others than the Hello World version of Flutter a.k.a Counter App, you might need to implement different ways of communicate events between Widgets, at least for me this was the path that guide me to communicate Widgets with Streams.
What if we pose the problem we will face off and how we will solve it?
Suppose we have to create an App for Client Management this must have a Screen with 3 tabs that shows information about the client: general info (name, phone and so on), services and invoices using Page-View Widget, the requirement is to handle show a unique FloatingActionButton that triggers the appropriate action depending on what page is shown an allow the business logic to save the information of every child page is executed in base of a parent action dispatched from widget that host the Page-View, what it means, that every child widget must to know when in the parent the button for saving information is pressed, the final screen must look like this:
TLTR; if you wanna go direct to the source code and check by your self you can get the source code here
Let’s Start
- We will start creating the basic structure, first create an Flutter App and Open with Visual Studio Code or Android Studio.
$ flutter create widget_comm
- Open the main.dart file and replace everything with the following code:
This code will create a basic structure a Page with a Bottom Navigation Bar with a Page View and three pages, at this point every page is only a container with a different color to see the changes when we push the every action in the navigation bar, if everything is OK you must see in your emulator something like this:
- Now it’s time to create three simple pages(widgets because everything in flutter is a widget) to interact with our floating action button, create three StatefulWidget with the name you want, one for General Info one for Service and one for Invoices, in my case the code for every page is the following: (if you want you can copy and paste it in your files)
Also is needed add the connection with the main.dart and the pages created, to accomplish we must change the following code on the main.dart:
Now that everything is structural connected, we need to communicate the parent with every child. We will get this with the following 5 steps :
- Create an Enum to define the events (This is not needed but is recommendable for easy code reading)
- Create a Stream that Emits the values defined in our Enum
- Modify our Pages to receive our stream as Param
- Create a Subscription for our Stream in every Child to parse the event received and fire the code we want
- Determine the page shown in base of a page index handled by PageView and sink the correspondent event.
- Create enum: this could be created in his own file or inside the main.dart, the code is really simple and you can create it in the main.
enum ClientEvents {SaveGeneralInfo,SaveServices,SaveInvoices}
2. Inside our main.dart file we need to create our Stream:
//
StreamController<ClientEvents> events = StreamController<ClientEvents>.broadcast();
We must use the broadcast() method because we gonna handle many listeners if we are sure that only one listener will be active at time you can omit broadcast() method and create as:
StreamController<ClientEvents> events = StreamController<ClientEvents>();
And don’t forget to close it in the dispose method.
@override
void dispose() {
events.close();
super.dispose();
}
3. Modify the pages to receive the Stream as Param, adding a parameter to the constructor, this code is the same in every Page only changes the name of the constructor because it must be related to the class.
class ClientInformationPage extends StatefulWidget{ final Stream<ClientEvents> parentEvents;
ClientInformationPage({@required this.parentEvents}); @override
ClientInformationPageState createState() => ClientInformationPageState();}
4. Create a Subscription in every child to listen the events and parse them in the State Class.
Create a variable to store Sub ( because we need to cancel on dispose() )
StreamSubscription<ClientEvents> _subEvents;
In the initState set the listener and eventually parse the event (for the sake of brevity a will set everything in the initState)
_subEvents = widget.parentEvents.asBroadcastStream().listen((event) { if (event == ClientEvents.SaveGeneralInfo){
print("Information: ${_clientNameController.text} / ${_clientPhoneController.text}");
}
});
As we set the Stream as broadcast() we must suscribe with asBroadcastStream()
5. Modify the main.dart to fire the events in the sink of the stream in the FloatingActionButton onPressed() event:
FloatingActionButton(
onPressed: () {
if (_pageIndex == 0) {
events.sink.add(ClientEvents.SaveGeneralInfo);
} else if (_pageIndex ==1) {
events.sink.add(ClientEvents.SaveServices);
} else if (_pageIndex == 2) {
events.sink.add(ClientEvents.SaveInvoices);
}
},
tooltip: 'Save',
child: Icon(Icons.save),
)
After all this step completed, you can run the application and at this moment we are only print a message in the console to identify if every event is fired and listened as we setup, checking the log will show us:
As you see every time we push the FloatingActionButton in every page the message is different, because every page listen only his events.
I hope this helps you to create awesome applications in Flutter, and if you find some bug or something to enhance we are all learning and every advice is well received don’t hesitate and comment.
You can find the source code in this repository
Thanks & Happy Coding!