Provider: Provide a StreamProvider based on a different Provider (ProxyProvider)

Created on 12 Jun 2019  路  9Comments  路  Source: rrousselGit/provider

Hi Remi,

I'd like to be able to expose a stream from one of my services as a StreamProvider globally, the stream is in another provider so this is what I've come up with for now.

List<SingleChildCloneableWidget> getProviders() {
  return [
    ...independentServices,
    ...dependentServices,
    ...uiConsumableProviders,
  ];
}

List<SingleChildCloneableWidget> independentServices = [
  Provider.value(value: Api())
];

List<SingleChildCloneableWidget> dependentServices = [
  ProxyProvider<Api, AuthenticationService>(
    builder: (context, api, authenticationService) =>
        AuthenticationService(api: api),
  )
];

// Important part here
List<SingleChildCloneableWidget> uiConsumableProviders = [
  ProxyProvider<AuthenticationService, StreamProvider<User>>(
    builder: (context, authService, initialProvider) =>
        StreamProvider.value(value: authService.userController.stream),
  )
];

How can I expose the stream from the AuthenticationService to the app so that it's consumable in this manner

Text(
   'Welcome ${Provider.of<User>(context).name}',
   style: headerStyle,
 ),

Currently I'm getting "Couldn't find the correct Provider above this widget" message. Am I on the right track?

Most helpful comment

Hi!
FWIW, I made a fork of your example here: https://github.com/rrousselGit/provider-example/blob/b87ea2537bab55836bcf4e3267fbdfbdce4ebd53/lib/main.dart#L32

It shows how I solved the issue.


For the story, I haven't made a Stream/Future variant of ProxyProvider because I don't see any safe way to do so.
We can't read from them synchronously, and they are usually used to trigger side-effects.
That combined with the fact that most peoples uses ChangeNotifier instead of immutable architectures means that a FutureProxyProvider may trigger unwanted http calls.

I'm thinking about it. But I currently can't see any way to prevent that from happening.
And considering provider aims at enforcing safe code, I decided not to include such risky variant.


All of that considered, for now I'd recommend to use the classical variant of Future/Stream providers, and use Provider.of inside the builder:

StreamProvider(
  builder: (context) => Provider.of<Foo>(context, listen: false).myStream,
  child: Something(),
)

That's acceptable because it will work only with listen: false, which makes the limitation explicit.

For more complex cases, make a custom StatefulWidget

All 9 comments

Hi!
FWIW, I made a fork of your example here: https://github.com/rrousselGit/provider-example/blob/b87ea2537bab55836bcf4e3267fbdfbdce4ebd53/lib/main.dart#L32

It shows how I solved the issue.


For the story, I haven't made a Stream/Future variant of ProxyProvider because I don't see any safe way to do so.
We can't read from them synchronously, and they are usually used to trigger side-effects.
That combined with the fact that most peoples uses ChangeNotifier instead of immutable architectures means that a FutureProxyProvider may trigger unwanted http calls.

I'm thinking about it. But I currently can't see any way to prevent that from happening.
And considering provider aims at enforcing safe code, I decided not to include such risky variant.


All of that considered, for now I'd recommend to use the classical variant of Future/Stream providers, and use Provider.of inside the builder:

StreamProvider(
  builder: (context) => Provider.of<Foo>(context, listen: false).myStream,
  child: Something(),
)

That's acceptable because it will work only with listen: false, which makes the limitation explicit.

For more complex cases, make a custom StatefulWidget

Thanks for the quick response. I have absolutely no idea why I didn't think of it that way it's probably the effect of trying to show off ProxyProvider that I wanted to do it using that only 馃槄

I see what you mean with the implementation, it makes sense to me. It's a good idea to "protect" devs from themselves, but you could also "allow bad behaviour" and give use guidelines on how to use it.

That way some of the more experienced devs can still make use of those features and also show where to use and not to use it. Either way, proxy provider on it's own is already a massive +1000 from me. I'll update my code and see if it works then I'll come back and close this issue.

The problem is, there's no linter rule that prevents beginners from using something they don't fully understand :nerd_face:

With Provider, I _really_ want to avoid complexity and make it the safest solution.
ProxyProvider is already a bit too much.

If that's your thing, I'd recommend combining provider with flutter_hooks + functional_widget.
This reduces the boilerplate needed to make a StreamProxyProvider without impacting the safety.

A provider+flutter_hooks + functional_widget example would look like:

@hwidget
Widget myUserProxy(BuildContext context, { Widget child }) {
  final snapshot = useStream(Provider.of<AuthenticationService>(context).user);
  return Provider.value<User>(
    value: snapshot.data,
    child: child,
  );
}

I can even modify functional_widget to optionally implement SingleChildCloneableWidget for the generated class to work with MultiProvider.

The difference between that and StreamProxyProvider is that more complex cases work just as well.

For example, the following combines multiple providers to make an HTTP request.

@hwidget
Widget myUserProxy(BuildContext context, { Widget child }) {
  final userId = Provider.of<User>(context).id;
  final host = Provider.of<Configurations>(context).host;

  final stream = useMemoized(() => doSomeHttpRequest(host, id), [host, id]);
  final snapshot = useStream(stream);

  return Provider.value<Foo>(
    value: snapshot.data,
    child: child,
  );
}

That's not something StreamProxyProvider could represent safely.

I get it. Keeping it safe is probably the best way to "teach" how to write better code without explicit instructions.

A provider+flutter_hooks + functional_widget example would look like:

I must say this provider + functional widget setup looks pretty nice. I'll definitely be looking at that as well. That's an awesome tip. Thanks man.

And to conclude. I used your example on how to provide the stream and it worked.

Hey!
Is this a valid approach for stream proxy?

class MyUserProxy extends StatelessWidget {
  final Widget child;

  const MyUserProxy({this.child});

  @override
  Widget build(BuildContext context) {
    final String uid = Provider.of<String>(context);
    return StreamBuilder<User>(
      stream: Repository.streamUser(uid),
      builder: (context, snapshot) {
        return Provider<User>.value(
          value: snapshot.data,
          child: child,
        );
      },
    );
  }
}

No you'll want to do the Repository.streamUser(uid) in DidChangeDependencies

Similarly use StreamProvider.value, not `StreamBuilder + Provider.value

No you'll want to do the Repository.streamUser(uid) in DidChangeDependencies

Similarly use StreamProvider.value, not `StreamBuilder+Provider.value

is it possible to do it with a stateless widget?

No, it's not.

Was this page helpful?
0 / 5 - 0 ratings