I am trying to use a StreamProvider
(from this awesome package), but I've been struggling to get a particular stream to work.
I create a StreamController
which I use to add data to its Stream
via its Sink
. All of this seems to be working fine. But when using this Stream
with a StreamProvider
, the widget tree does not reflect the changes of the Stream
. It does however work fine using a StreamBuilder
.
The code using a StreamProvider
:
class TestPage extends StatelessWidget {
final Mockup exchange = ExchangeApi.mockup;
@override
Widget build(BuildContext context) {
return StreamProvider<List<Data>>(
builder: (BuildContext context) => Wrapper.mockup.stream,
initialData: null,
catchError: (BuildContext context, e) {
print("Error: $e");
return null;
},
child: TestPageBody(),
);
}
}
class TestPageBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
List<Data> dataList = Provider.of<List<Data>>(context);
return ListView.builder(
itemCount: dataList?.length ?? 0,
itemBuilder: (BuildContext context, int index) {
return Text(dataList[index].name);
},
);
}
}
I have been searching why this does not work, but haven't found an answer yet. But here are some things I did find:
Any help would be greatly appreciated!
The StreamProvider exposes the value to its descendants which means you can’t use the same “context” as StreamProvider. And a solution here is to create a new widget that will be used as a child of StreamProvider. When you create a widget to manipulate each stream, it makes your code more readable and easier to maintain.
The StreamProvider is a class that listens to a stream and exposes the value to its descendants. To work with it we just need to set: and the “ value ” that is the stream that we will listen to.
But to work with it, we need to configure our project first, adding the Provider reference in pubspec.yaml file: In other words, ChangeNotifierProvider (among other things) provides a way that you can instantiate a class that extends ChangeNotifier (DI) and through the Consumer, the descendants widgets will be notified and rebuilt.
The StreamController provides the StreamSink class, allowing us to insert something inside the pipe (using the sink property) and getting the value that was inserted, the Stream, using the stream property. Let’s create a class that will handle this.
The default behavior of most providers (excluding ChangeNotifierProvider
) is to assume that the values passed are immutable.
As such, if your stream emits the same value as previously emitted, this won't rebuild dependents.
There are two solutions:
Make the values emitted by your stream immutable, such that performing previousValue == newValue
works correctly.
override updateShouldNotify
to not filter values if they didn't change.
A simple updateShouldNotify: (_, __) => true
will do.
In the ideal world, prefer immutability.
This can be done by making a copy of your object before sending it to streamController.add(value)
:
List<T> value;
streamController.add(List.from(value));
A second (optional) step is to override updateShouldNotify
(or operator==
on your object) to notify the provider that the value didn't change.
With List
, this can be done using ListEquality
from collection/collection.dart
:
import 'package:provider/provider.dart';
import 'package:collection/collection.dart';
StreamProvider<List<int>>(
builder: (_) => stream,
updateShouldNotify: const ListEquality<int>().equals,
);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With