8

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:

  • Using Flutter Desktop Embedding, the UI did reflect changes from the stream when the window got resized (thus forcing to rebuild). The same effect is seen when using hot refresh.
  • The stream is constantly adding new data, I tested this by debugging and simply printing out the data

Any help would be greatly appreciated!

Bram Vanbilsen
  • 5,763
  • 12
  • 51
  • 84
  • If you add an `updateShouldNotify: (_, __) => true`, does this work? (this is just to confirm something) – Rémi Rousselet Aug 05 '19 at 19:53
  • That's it! Thanks again Rémi... But, could you clarify why this is necessary? It never seemed to be necessary with other streams I used (although this could be a coincidence). I thought the property's use case would be something like performance (limit re-rendering). – Bram Vanbilsen Aug 05 '19 at 19:59

1 Answers1

20

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,
);
Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • 1
    This makes sense, as I was adding to a `List` and was not actually creating a new `List` object. Thanks for the quick reply and thanks for the great package! – Bram Vanbilsen Aug 05 '19 at 20:14