Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

StreamProvider not updating state

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!

like image 486
Bram Vanbilsen Avatar asked Aug 05 '19 19:08

Bram Vanbilsen


People also ask

Why can’t I use the context of a streamprovider?

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.

What is a streamprovider in Java?

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.

How to work with changenotifierprovider?

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.

How do I use the streamsink class in a streamcontroller?

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.


1 Answers

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,
);
like image 154
Rémi Rousselet Avatar answered Nov 09 '22 03:11

Rémi Rousselet