Kevin
Kevin

Reputation: 341

Trouble rebuilding a StreamProvider to update its current data

so I have aConsumer<NotificationProvider> and in its builder function a StreamProvider<List<Item>>.

Please note that the latter widget builds perfectly on its initial load.

NotificationProvider contains a list of notifications from Firebase Cloud Messaging, ergo when I receive a notification, I push something into the class' Listand then callnotifyListeners()`.

Please also note that my NotificationProvider is doing a good job because I have a counter at my AppBar and it's updating whenever I receive one.

Now on to the meat and potato.

I'm trying to rebuild the StreamProvider whenever NotificationProvider.addAlert() is called. But somehow it's not working?

I also added updateShouldNotify: (prev, next) => true, but it didn't help one bit.

Please help. Thanks!

I also added updateShouldNotify: (prev, next) => true, on the StreamProvider properties but it didn't help one bit.

return Consumer<NotificationProvider>(
  builder: (context, provider, child) {
    return StreamProvider<ItemsProvider>(
      builder: (_) async* {
        ItemsProvider _itemsProvider = Provider.of<ItemsProvider>(context);
        await _itemsProvider.getItems();
        yield _itemsProvider;
      },
      child: LeContent(),
      updateShouldNotify: (prev, next) => true,
    );
  },
);

I expect the list to update whenever I receive an FCM notification

Upvotes: 0

Views: 1495

Answers (2)

Kevin
Kevin

Reputation: 341

I solved my own issue by having a StreamBuilder in a StatefulWidget.

I have my fetcher as a separate Future<T> method which will be called on initState and on didUpdateWidget().

Next, in my StatefulWidget class, I require something that will be used to validate on didUpdateWidget()'s oldWidget.something != widget.something.

In my case I used the length of the NoticeProvider.notices which will be incremented every time my FCM configuration triggers it.

in code:

  @override
  void didUpdateWidget(_Body oldWidget) {
    if (oldWidget.alertLength != widget.alertLength) _fetchData();
    super.didUpdateWidget(oldWidget);
  }

_fetchData, will then call the api, transform the JSON, and add it to the stream using a StreamController which, in turn, update the UI.

I hope someone will be helped by this answer in the Future!

Upvotes: 0

R&#233;mi Rousselet
R&#233;mi Rousselet

Reputation: 276957

The builder parameter is called exactly once for the entire life of the StreamProvider.

The fact that you called Provider.of<ItemsProvider> doesn't change anything here – the method still won't be called again, even if ItemsProvider changes.

If you insist in using StreamProvider, you'll need to somehow transform the Provider.of into a stream instead.

You can use a StatefulWidget to do so. Here's an example:

class ProviderToStream<T> extends StatefulWidget {
  const ProviderToStream({Key key, this.builder, this.child}) : super(key: key);

  final ValueWidgetBuilder<Stream<T>> builder;
  final Widget child;

  @override
  _ProviderToStreamState<T> createState() => _ProviderToStreamState<T>();
}

class _ProviderToStreamState<T> extends State<ProviderToStream> {
  final StreamController<T> controller = StreamController<T>();

  @override
  void dispose() {
    controller.close();
    super.dispose();
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    controller.add(Provider.of<T>(context));
  }

  @override
  Widget build(BuildContext context) {
    return widget.builder(context, controller.stream, widget.child);
  }
}

You can then do:

ProviderToStream<Foo>(
  builder: (_, stream, __) {
    return StreamProvider(
      builder: (_) async* {
        await for (final value in stream) {
          // TODO: yield something
        }
      }
    );
  }
)

Upvotes: 1

Related Questions