1

I'm using FirebaseAuth and Firestore for my application.

'User' is a model which represent a document on firestore and use uid from FirebaseUser to be its DocumentId as well.

In order to get a 'User', I have to pass in uid from FirebaseUser.

My idea is setup a StreamProvider of FirebaseUser, and then setup another StreamProvider of User, which takes FirebaseUser as a param.

But error occurs, I couldn't found the solution for this issue, any advice would help me very much.

List<SingleChildCloneableWidget> uiConsumableProviders = [
  StreamProvider<FirebaseUser>(
    builder: (context) => Provider.of<LoginNotifier>(context, listen: false).streamFirebaseUser,
  ),
  StreamProvider<User>(
    builder: (context) =>
        Provider.of<UserNotifier>(context, listen: false).streamUser(Provider.of<FirebaseUser>(context)),
  ),
];
I/flutter (15813): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (15813): The following assertion was thrown building InheritedProvider<FirebaseUser>:
I/flutter (15813): inheritFromWidgetOfExactType(InheritedProvider<FirebaseUser>) or inheritFromElement() was called
I/flutter (15813): before BuilderStateDelegate<Stream<User>>.initDelegate() completed.
I/flutter (15813): 
I/flutter (15813): When an inherited widget changes, for example if the value of Theme.of()
I/flutter (15813): changes, its dependent widgets are rebuilt. If the dependent widget's reference
I/flutter (15813): to the inherited widget is in a constructor or an initDelegate() method, then
I/flutter (15813): the rebuilt dependent widget will not reflect the changes in the inherited
I/flutter (15813): widget.
I/flutter (15813): 
I/flutter (15813): Typically references to inherited widgets should occur in widget build()
I/flutter (15813): methods. Alternatively, initialization based on inherited widgets can be placed
I/flutter (15813): in the didChangeDependencies method, which is called after initDelegate and
I/flutter (15813): whenever the dependencies change thereafter.
I/flutter (15813):
arun v
  • 852
  • 7
  • 19
Joe Ng
  • 91
  • 2
  • 7
  • It's not supported yet. It's unclear how it'd behave because `builder` is called only once but the value obtained from another provider can change over time. – Rémi Rousselet Aug 28 '19 at 08:47
  • @RémiRousselet yes, that's exactly why i'm stuck. My purpose is I want to get User data from anywhere in widget tree, so I need sth like a stream. Would you please give me some suggestion for this issue? – Joe Ng Aug 28 '19 at 09:01

2 Answers2

2

You cannot call Provider.of inside builder.

But you can transform Provider.of into a stream, using a widget, then use that stream in builder of StreamProvider.

Here's a widget that does it for you:

class ProviderToStream<T> extends StatefulWidget
    implements SingleChildCloneableWidget {
  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>();

  @override
  ProviderToStream<T> cloneWithChild(Widget child) {
    return ProviderToStream(
      key: key,
      builder: builder,
      child: child,
    );
  }
}

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);
  }
}

Then you can do:

MultiProvider(
  providers: [
    StreamProvider<Foo>(builder: (_) => Stream.empty()),
    ProviderToStream<Foo>(
      builder: (_, foo, child) => StreamProvider<String>(
        builder: (_) async* {
          await for (final value in foo) {
            yield value.toString();
          }
        },
        child: child,
      ),
    ),
  ],
  child: MaterialApp(),
);
Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • Hi Remi, what if I just want to do this: MultiProvider( providers: [ StreamProvider.value(value: AppAuth.currentUser), StreamProvider.value( value: AppDb.getUserData(Provider.of(context)?.uid), ], but the UserDataModel needs the input first from the uid of AuthUser? – leemuljadi Jan 16 '20 at 10:17
  • What packages need to be included for this to work? I'm getting unresolved references to SingleChildCloneableWidget – Curt Eckhart Jan 17 '20 at 03:55
1

After some workarounds, I've solved my issue by combining Observable from rxdart with Provider. Like this:

Stream<User> get userStream {
return Observable(_firebaseAuth.onAuthStateChanged).switchMap((user) {
  if (user != null) {
    return userCollection
        .document(user.uid)
        .snapshots()
        .map((doc) => User.fromDocumentSnapshot(doc))
        .handleError(_catchError);
  } else {
    return Observable<User>.just(null);
  }
});}

Outside MultiProvider:

StreamProvider<User>(builder: (context) => Provider.of<FireStoreService>(context, listen: false).userStream,),

Hope this will help anyone facing the same issue.

Joe Ng
  • 91
  • 2
  • 7