Pass user uid to Firestore stream query in Flutter

1,412

Solution 1

You could try a switchmap from rxdart which listens to and maps a stream T to one of S.

Stream<S> switchMap<S>(Stream<S> Function(T value) mapper) =>
      transform(SwitchMapStreamTransformer<T, S>(mapper));
import 'package:rxdart/transformers.dart';

final userStream = AuthProvider().user;

final itemsStream = userStream.switchMap<DocumentSnapshot>(
  (user) {
    if (user == null) return Stream<DocumentSnapshot>.value(null);

    return itemsCollection.where('uid', isEqualTo: user.uid).snapshots();
  },
);
StreamProvider<DocumentSnapshot>.value(
  value: itemsStream,
),

Writing this off the cuff so it might be wrong.

Edit

Another way would just be to place your items provider one level below your user. That way you can just regularly consume the user uid value.

return StreamProvider<User>.value(
  value: AuthProvider().user,
  child: Consumer<User>(
    builder: (_, user, __) {
      if (!user) {
        // Not logged in
      } else {
        return MultiProvider(
          providers: [
            Provider.value<DatabaseService>(
              value: DatabaseService(user.uid),
            ),
            // More services that rely on user.uid
          ],
          child: SizedBox(),
        );
      }
    },
  ),
);

2 Edit

class DatabaseService {
  DatabaseService(this.uid);

  final String uid;

  getData() {
    return MyCollection.where('uid', isEqualTo: uid).snapshots();
  }
}

Solution 2

This seems to have worked having main.dart looking as below:

Main.dart


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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamProvider<User>.value(
      value: AuthProvider().user,
      child: Consumer<User>(
        builder: (context, user, __) {
          if (user == null) {
            return MaterialApp(
              debugShowCheckedModeBanner: false,
              theme: ThemeData.dark(),
              title: 'Sendr',
              home: LoginScreen(),
            );
          } else {
            return MultiProvider(
              providers: [
                StreamProvider<DocumentSnapshot>.value(
                  value: DatabaseService(user.uid).userRoutesDone,
                ),
              ],
              child: MaterialApp(
                debugShowCheckedModeBanner: false,
                theme: ThemeData.dark(),
                title: 'Sendr',
                home: NavigatorScreen(),
                routes: {
                  SplashPage.id: (context) => SplashPage(),
                  LoginScreen.id: (context) => LoginScreen(),
                  NavigatorScreen.id: (context) => NavigatorScreen(),
                  MapScreen.id: (context) => MapScreen(),
                },
              ),
            );
          }
        },
      ),
    );

My Notifier:

import 'package:cloud_firestore/cloud_firestore.dart';

class DatabaseService {
  DatabaseService(this.uid);

  final String uid;

  Stream<DocumentSnapshot> get itemsInUserDocument {
    final DocumentReference userData =
        Firestore.instance.collection('userdata').document(uid);
    return userData.snapshots();
  }
}

Where I used it down the widget tree:


  @override
  Widget build(BuildContext context) {
    final userTaskDone = Provider.of<DocumentSnapshot>(context);

    List taskList = [];    <-- (3) Using the list here. 

    taskList = userTaskDone['tasks'].toList();

    print(taskList);


Share:
1,412
Wesley Barnes
Author by

Wesley Barnes

Updated on December 21, 2022

Comments

  • Wesley Barnes
    Wesley Barnes over 1 year
    1. Here I am using provider in main.dart to stream "itemsInUserDocument" data from a document.
    
    Widget build(BuildContext context) {
        return MultiProvider(
          providers: [
              StreamProvider<DocumentSnapshot>.value(
              value: DatabaseService().itemsInUserDocument,      <--- (1) Providing here
              ),
              StreamProvider<User>.value(
              value: AuthProvider().user,
              ),
          ],
    
    
    1. My problem is passing the userUID to the Stream query here. My document id's on Firestore is my userUID.
    
    final userUID = "3SlUigYBgsNgzjU8a9GSimhAhuu1";            <-- (2) Need to pass to stream "userID' below??
    
    
    class DatabaseService {
    
      Stream<DocumentSnapshot> get itemsInUserDocument {
    
        final DocumentReference userData = Firestore.instance.collection('userdata').document(userUID);
    
        return userData.snapshots();
      }
    }
    
    
    1. Here I am using the list in down the widget tree.
      @override
      Widget build(BuildContext context) {
        final userTaskDone = Provider.of<DocumentSnapshot>(context);
    
        List taskList = [];    <-- (3) Using the list here. 
    
        taskList = userTaskDone['tasks'].toList();
    
        print(taskList);
    
    
    
    • It currently works and I am getting the streamed data in section (3) as is now, but I need to pass the Firebase user UID into the stream in section (2).

    • I can get the user UID in a stateful widget with setState function but everything I have tried doesn't work or seems like I am duplicating things too much, I am trying to use Provider to manage state properly.

    • I can get the userUID with provider as well, but you can only use it on a (context), where my notifier section (2) is only a class.

    • PLEASE help me with a solution.

    EDIT:

    I have tried the edits and the result is as follow:

    Getting this critical error from provider when I enter the screen where it used to work before these changes to main.dart.

    enter image description here

    Here is the full code for main.dart with edits as it is now:

    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          theme: ThemeData.dark(),
          title: 'Sendr',
          home: MainScreen(),
          routes: {
            SplashPage.id: (context) => SplashPage(),
            LoginScreen.id: (context) => LoginScreen(),
            NavigatorScreen.id: (context) => NavigatorScreen(),
            CragSelectionScreen.id: (context) => CragSelectionScreen(),
            CragRoutesScreen.id: (context) => CragRoutesScreen(),
            RouteDetailScreen.id: (context) => RouteDetailScreen(),
            MapScreen.id: (context) => MapScreen(),
          },
        );
      }
    }
    
    ///Authentication Logic
    class MainScreen extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return StreamProvider<User>.value(
          value: AuthProvider().user,
          child: Consumer<User>(
            builder: (context, user, __) {
              if (user == null) {
                return LoginScreen();
              } else {
                return MultiProvider(
                  providers: [
                    Provider<DatabaseService>.value(
                      value: DatabaseService(user.uid),
                    ),
                  ],
                  child: NavigatorScreen(),
                );
              }
            },
          ),
        );
      }
    }
    
    

    This is my Database Service:

    import 'package:cloud_firestore/cloud_firestore.dart';
    
    class DatabaseService {
      DatabaseService(this.uid);
    
      final String uid;
    
      Stream<DocumentSnapshot> get itemsInUserDocument {
        final DocumentReference userData =
            Firestore.instance.collection('userdata').document(uid);
        return userData.snapshots();
      }
    }
    
    

    I have made no change to my step (3) where I am using it, is this where my problem might be now?

    I am trying to do more research on this. Working through the provider docs. If I understand your suggestion correctly, we are streaming the User value from AuthProvider at the top, then consume the User value just below it then passing the user.uid value to the DatabaseService, which is used down the widget tree again. Looks like I am almost there from your help. If you don't mind, please let me know what you think of the provider error on the screen. Much appreciated.