Combine two Streams into one StreamProvider
Asked Answered
S

2

3

I have two streams:

  • Stream<FirebaseUser> FirebaseAuth.instance.onAuthStateChanged
  • Stream<User> userService.streamUser(String uid)

My userService requires the uid of the authenticated FirebaseUser as a parameter.

Since I will probably need to access the streamUser() stream in multiple parts of my app, I would like it to be a provider at the root of my project.

This is what my main.dart looks like:

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  Widget build(BuildContext context) {
    var auth = FirebaseAuth.instance;
    var userService = new UserService();
    return MultiProvider(
      providers: [
        Provider<UserService>.value(
          value: userService,
        ),
      ],
      child: MaterialApp(
        home: StreamBuilder<FirebaseUser>(
            stream: auth.onAuthStateChanged,
            builder: (context, snapshot) {
              if (!snapshot.hasData) return LoginPage();
              return StreamProvider<User>.value(
                value: userService.streamUser(snapshot.data.uid),
                child: HomePage(),
              );
            }),
      ),
    );
  }
}

The issue is that when I navigate to a different page, everything below the MaterialApp is changed out and I lose the context with the StreamProvider.

Is there a way to add the StreamProvider to the MultiProvider providers-list? Because when I try, I also have to create another onAuthStateChanged stream for the FirebaseUser and I don't know how to combine them into one Provider.

Seibert answered 6/10, 2019 at 17:24 Comment(1)
most likely you need flatMap or concatMap / asyncExpandAyr
S
0

So this seems to work fine:

StreamProvider<User>.value(
  value: auth.onAuthStateChanged.transform(
    FlatMapStreamTransformer<FirebaseUser, User>(
      (firebaseUser) => userService.streamUser(firebaseUser.uid),
    ),
  ),
),

If anybody has doubts about this in certain edge cases, please let me know.

Thanks to pskink for the hint about flatMap.

Seibert answered 6/10, 2019 at 19:15 Comment(4)
why do you need FlatMapStreamTransformer? cannot you do that using ordinary Observable.flatMap method? i know its pretty much the same but...Ayr
Because VsCode tells me, that "The method 'flatMap' isn't defined for the class 'Stream'" and I don't know why, because I imported 'package:rxdart/rxdart.dart', but this way worked right away.Seibert
because it's is Observable method - use main Observable constructor to create itAyr
something like: Observable(auth.onAuthStateChanged).flatMap(...)Ayr
I
0

Maybe you can try this approach:

main.dart

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        Provider<FirebaseUser>(
          builder: (_) => FirebaseUser(),
        ),
      ],
      child: AuthWidgetBuilder(builder: (context, userSnapshot) {
        return MaterialApp(
          theme: ThemeData(primarySwatch: Colors.indigo),
          home: AuthWidget(userSnapshot: userSnapshot),
        );
      }),
    );
  }
}

AuthWidgetBuilder.dart

Used to create user-dependant objects that need to be accessible by all widgets. This widget should live above the [MaterialApp]. See [AuthWidget], a descendant widget that consumes the snapshot generated by this builder.

class AuthWidgetBuilder extends StatelessWidget {
  const AuthWidgetBuilder({Key key, @required this.builder}) : super(key: key);
  final Widget Function(BuildContext, AsyncSnapshot<User>) builder;

  @override
  Widget build(BuildContext context) {

    final authService =
        Provider.of<FirebaseUser>(context, listen: false);
    return StreamBuilder<User>(
      stream: authService.onAuthStateChanged,
      builder: (context, snapshot) {

        final User user = snapshot.data;
        if (user != null) {
          return MultiProvider(
            providers: [
              Provider<User>.value(value: user),
              Provider<UserService>(
                builder: (_) => UserService(uid: user.uid),
              ),
            ],
            child: builder(context, snapshot),
          );
        }
        return builder(context, snapshot);
      },
    );
  }
}

AuthWidget.dart

Builds the signed-in or non signed-in UI, depending on the user snapshot. This widget should be below the [MaterialApp]. An [AuthWidgetBuilder] ancestor is required for this widget to work.

class AuthWidget extends StatelessWidget {
  const AuthWidget({Key key, @required this.userSnapshot}) : super(key: key);
  final AsyncSnapshot<User> userSnapshot;

  @override
  Widget build(BuildContext context) {
    if (userSnapshot.connectionState == ConnectionState.active) {
      return userSnapshot.hasData ? HomePage() : SignInPage();
    }
    return Scaffold(
      body: Center(
        child: CircularProgressIndicator(),
      ),
    );
  }
}

This is originally from the tutorial of advance provider from Andrea Bizotto. But I tailored some the code according to your your code above. Hope this works, good luck!

Reference: https://www.youtube.com/watch?v=B0QX2woHxaU&list=PLNnAcB93JKV-IarNvMKJv85nmr5nyZis8&index=5

Ide answered 1/12, 2019 at 6:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.