Share Streams with InheritedWidget

Tino Kallinich
Flutter Community

--

I’m working with Flutter since late 2018, just before the version 1.0 has been released. As you can see in my other articles I y huge fan of micro apps. Apps which cover a single topic or fulfill a certain task. I use this approach where ever I start experimenting with new features.

Streams are of course no new features, but working with them can result in large projects. In this article I will use micro-app to demonstrate how we can use streams and access them every where in our app.

“Can I connect to a stream every where in my app using the InheritedWidget?”

Before I start I will clarify what a BLoC, a Stream, a StreamController and a StreamBuilder is. This micro-app should be able to connect to a stream of data. We will attach the stream data to a widget.This will result in a live update of the widget with the stream data. Doing this for a single page application is quite easy, but how so in app with more then one page.

The micro-app in this article has a minimum of two pages and will serve as an example of stream sharing across a Flutter application.

Definitions

BLoC

The Business Logic Component can be seen as a wrapper of all your code which is not UI related. BloC is a pattern which helps you to structure your project into UI related code and business logic.

For me as a starter this means, you can create a bloc.dart component and collect all you functional code in it. Additional you can create a widget.dart which calls the component in order to populate its view.

Streams

In nature a stream, like the gulf-stream, is a current of a medium. The gulf-stream moves the medium water around the world. In code we can think of stream in the same way, it sends data from A to B.

StreamController

The StreamController controls the Stream, meaning he can expose and manipulate the Stream accordingly.

          /* A controller with the stream it controls. */

StreamBuilder

The StreamController exposes the Stream and the StreamBuilder can retrieve any Stream data in a snapshot.

StreamBuilder(
initialData: " ...",
stream: /* some stream */,
builder: (context, snapshot) { return Container(...); }
)

The StreamBuilder is a widget which can be placed anywhere in your app. For example you could declare it a the top level of you widget tree (root widget) and trickle down the stream reference. In this article I decided to combine the concept of InheritedWidgets and Streams.

Get Startet

Create three pages LandingPage.dart, FirstPage.dart and SecondPage.dart. Add routing as you prefer, so we can navigate between the pages. Please see my GitHub repository (flutter_app_stream) for reference.

Create a Stream in a BLoC

// TestBloc.dartclass TestBloc{

int counter = 0;

Stream <int> getPeriodicStream() async* {

counter++;
yield* Stream.periodic(Duration(seconds: 1),(_) {
var value = new Random().nextInt(100);
return value;
});

}
}

The generated Stream sends random numbers between 0 and 100 in a 1 sec interval.

Create a InhertedWidget

With the InheritedWidget I can share any data between all child widgets. Here I wraped the InheritedWidget around the root widget. Now we can access all data from the InhertedWidget everywhere in the app.

// GlobalValues.dartclass GlobalValues extends InheritedWidget {  // StreamController controller;
// Stream stream;
// Provider provider = new Provider(); * optional
// TestBloc testbloc = TestBloc();
// int counter = 0;
@override
bool updateShouldNotify(InheritedWidget oldWidget) => true;

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

static GlobalValues of(BuildContext context){
return context.inheritFromWidgetOfExactType(GlobalValues)
as GlobalValues;
}
}

The InheritedWidget is called GlobalValues in this project and article (name casting with the keyword as). We declare the StreamController, Stream and the BLoC here.

Setup the StreamController

The LandingPage.dart will initialize the Stream and setup the Stream-Controller by calling the BloC (bloc.getPeriodicStream()). The counter will take care that we initialize the Stream only ones.

class _LandingPageState extends State<LandingPage> {

StreamController ctrl;
TestBloc bloc = TestBloc();

int counter = 0;

@override
Widget build(BuildContext context) {

if(counter == 0){
// listen to multiple streams
ctrl = StreamController.broadcast();
// get and add a Stream
ctrl.addStream(bloc.getPeriodicStream());

counter++;
}

// get access to StreamController
GlobalValues.of(context).controller = ctrl;

return Scaffold(...)
}
}

The routing to the next page (FirstPage.dart) is hidden in the Scaffold widget. Feel free to create the UI for navigation and representation of data.

Setup the StreamBuilder

First task is to get the reference to the StreamController in order to get access to the Stream.

// FirstPage.dart and SecondPage.dart@override
Widget build(BuildContext context) {

controller = GlobalValues.of(context).controller;
...
}

Second we wrap the StreamBuilder around a widget. This widget and its child widget have now access to the Stream via the StreamBuilder stream attribute.

// FirstPage.dart and SecondPage.dartScaffold(
appBar: AppBar( ... ),
body: StreamBuilder(
initialData: "...",
stream: controller.stream,
builder: (context, snapshot) {
return new Text( ... )
}),
);

Get Stream Data

With the stream attribute we are listen to the stream and returns snapshots of it in the builder function. The snapshot holds the stream data. We can use it to populate widgets which are returned to its parent.

builder: (context, snapshot) {
return new Text(snapshot.data.toString);
}

With this setup we are able to listen to multiple Streams on multiple pages by sharing the StreamController with a InhertedWidget. As soon as your start the app the counter (the periodic stream) will start and you can navigate between all pages and see the value of the counter on each screen.

Optional

getText(AsyncSnapshot snapshot, BuildContext context){

String setString = 'Page ${snapshot.data.toString()}';

int value = int.tryParse(snapshot.data.toString()) ?? 1984;
if (value % 2 != 0) setString = "disconnected from stream";

return Text(setString, style: TextStyle(fontSize: 24),);
}

To manipulate the snapshot you can parse, modify and return value as you can see in the getText(…) function.

Recap

They Question was :

“Can I connect to a stream every where in my app using the InhertedWidget?”

The answer is yes, but before you use this structure you should search for the scope_model pattern. This has been a straight forward experiment, where I try to work through some new concepts and work with BLoC, Streams and InheritedWidgets.

I hope this article helps you to get started with Flutter and motivated you to create similar project articles.

Happy coding …

Business Blog

2D Animation Repository

Github Repository

--

--