1

I'm making an app that needs to display two separate home pages depending on whether or not a counselor value equals true on my Cloud Firestore Database. I am new to Object Oriented Programing, dart, and Firestore so please bear with me.

What I'm trying to do is initialize a variable called counselor and set that equal to the Counselor field value on my database. Then I wish to first check if the user has signed in and if they have signed it that's when I use a series of if statements to see whether or or not the counselor boolean equals true or false. and depending on the result it will display a certain homepage.

I get an error message on my app saying that the I'm returning a null value on my counselor widget. I suspect this is because I'm setting the counselor variable to equal the name of the field Counselor on my database and not it's actual boolean value. Problem is I'm not aware of the syntax to circumvent this problem.

Here is my code

    import 'package:strength_together/models/user.dart';
import 'package:strength_together/Screens/authenticate/authenticate.dart';
import 'package:strength_together/Screens/home/home.dart';
import 'package:strength_together/Screens/home/wrapper.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:strength_together/services/database.dart';
import 'package:strength_together/Screens/home/counsHome.dart';

class Wrapper extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final user = Provider.of<SUser>(context);
    bool counselor = getCounselor();
    //return either authenticate or home/couselor home
    if (user == null){
      return Authenticate();
    }else if(counselor == true){
    return CounselorHome();
    }else if(counselor == false){
    return Home();

    }
  }
}

// Database class

import 'package:cloud_firestore/cloud_firestore.dart';


class DatabaseService {

  final String uid;

  DatabaseService({this.uid});

  //collection reference
  final CollectionReference userCollection = FirebaseFirestore.instance
      .collection('User Data');

  Future updateUserData(String name, bool counselor) async {
    return await userCollection.doc(uid).set({
      'name': name,
      'Counselor': true,

    });
  }

  //get user data stream
  Stream<QuerySnapshot> get userData {
    return userCollection.snapshots();
  }

  Future<DocumentSnapshot> getDataSnapshotForCounselor() async
  {
    return await FirebaseFirestore.instance.collection('User Data')
        .doc(uid)
        .get();
  }

  bool getCounselorValue(DocumentSnapshot dataSnapshotForCounselor) {
    //modify this by passing proper keyValue to get counselor.
    return dataSnapshotForCounselor.data()['Counselor'];
  }
}

// auth class

import 'package:firebase_auth/firebase_auth.dart';
import 'package:strength_together/Screens/authenticate/register.dart';
import 'package:strength_together/models/user.dart';
import 'package:strength_together/services/database.dart';
import 'package:flutter/material.dart';

class AuthService{

  final FirebaseAuth _auth = FirebaseAuth.instance;

  //instance of counselor
  bool _counselor;

  bool get counselor => counselor;

  void setCounselor(bool counselor) {
    _counselor = counselor;
  }

  //create user obj based on firebase user from my code
  SUser _userFromFirebaseUser(User user){
    return user != null ? SUser(uid: user.uid) : null;
  }

  //auth change user stream
  Stream<SUser> get user {
    return _auth.authStateChanges()
        .map(_userFromFirebaseUser);
  }

  //method to sign in anon
  Future signInAnon() async{
    try{
      UserCredential result = await _auth.signInAnonymously();
      User user = result.user;
      return _userFromFirebaseUser(user);
    }catch(e){
      print(e.toString());
      return null;
    }
  }

  //method to sign in with email/pass
  Future signInWithEmailAndPassword(String email, String password) async{
    try{
      UserCredential result = await _auth.signInWithEmailAndPassword(email: email, password: password);
      User user = result.user;
      return _userFromFirebaseUser(user);
    }catch(e){
      print(e.toString());
      return null;
    }
  }

  //method to register with email/pass
  Future registerWithEmailAndPassword(String email, String password) async{
    try{
      UserCredential result = await _auth.createUserWithEmailAndPassword(email: email, password: password);
      User user = result.user;

      //create a new document for that user with the uid
      await DatabaseService(uid: user.uid).updateUserData('New user', _counselor);

      return _userFromFirebaseUser(user);
    }catch(e){
      print(e.toString());
      return null;
    }
  }

  //sign in with google

  //sign out
  Future signOut() async{
    try{
      return await _auth.signOut();
    }catch(e){
      print(e.toString());
      return null;
    }
  }

This is the database

1 Answers1

2

There are lots of problems with your code . First of all you are using Firestore.instance and .document which are deprecated . Use FirebaseFirestore.instance and FirebaseFirestore.instance.collection(...).doc(...) instead.

Coming to your code , Use a FutureBuilder to decide which page to show , while deciding , you can show a CircularProgressIndicator to the user.

Next , this code

Firestore.instance.collection('User Data').document('Counselor');

returns a DocumentReference and not the actual value that is required . So after you get the DocumentReference use .get() which returns a Future<DocumentSnapshot> , DocumentSnapshot class has a method data() which returns Map<String,dynamic>. Once you get the Map<> , you can simply get the data you want by passing the key value.

The whole process would look similar to this :

Future<bool> getCounselorValue() async {

return await FirebaseFirestore.instance.collection(...).doc(...).get().data()['keyvalue'];

}

Aagain , remember you are dealing with a Future so you have to use either await , .then() or FutureBuilder . I recommend FutureBuilder for your case , if you are not sure how to use it, refer to this . I also answered this which might be helpful if you want to dynamically decide which page to show to user first (refer to first method).

Edit:

Ok , so there was a little mistake in my code .Now I am sharing with you a little more code which you might find helpful .

make your class Stateful from Stateless and follow this code :

class Wrapper extends StatefulWidget{
WrapperState createState()=> WrapperState();
}

class WrapperState extends State<Wrapper>
{ 
  Future<DocumentSnapshot> documentSnapshot;
  
  User getUser(BuildContext context)
  {
  return Provider.of<User>(context);
  }

  dynamic getStartingPage(bool counselor,User user)
{
   if (user == null){
      return Authenticate();
    }else if(counselor == true){
      return CounselorHome();
    }else if(counselor == false){
      return Home();

  }
  }
  
 Future<DocumentSnapshot> getDocumentSnapshotForCounselor() async
 {
    return await FirebaseFirestore.instance.collection(...).doc(...).get();
 }

 bool getCounselorValue(DocumentSnapshot documentSnapshotForCounselor)
 {
    //modify this by passing proper keyValue to get counselor. 
   return  documentSnapshotForCounselor.data()['keyValue'];
 
 }

 @override
 void initState()
 {
  super.initState();
  documentSnapshot=getDocumentSnapshotForCounselor();
 
 }
 
 @override
 Widget build(BuildContext context) {
    return FutureBuilder(
      future:documentSnapshot,
      builder: (BuildContext ctx, AsyncSnapshot<dynamic> snapshot) {
        if (snapshot.connectionState != ConnectionState.done) {
          return Center( child : const CircularProgressIndicator());
        }
        else
          return getStartingPage(getCounselorValue(snapshot.data),getUser(context));
      }
    );
  }
 
}


Modify getDocumentSnapshotForCounselor and getCounselorValue methods ,so that you can get the value of the field from the database. Upgrade to latest version of cloud_firestore , visit this to understand how to do it and what to import .

Edit 2:

When you sign in user ,either by email password or using google sign in etc. It returns a Future<UserCredential> , UserCredential has a property user , which returns User. Now what we want is, a unique uid of the user which we can get using User.uid :

   UserCredential userCredential =await _auth.createUserWithEmailAndPassWord();
   
String uid= userCredential.user.uid;

Now create an entry for the user in the database using this uid:

FirebaseFirestore.instance.collection("User Data").doc(uid).set({"Counselor":true,"name":name});

Afterwards , to get the counselor value , :


FirebaseFirestore.instance.collection("User Data").doc(uid).get();

and 

documentSnapshotForCounselor.data()['Counselor'];

The above code is just for an explanation , make appropriate changes to your code.

Anurag Tripathi
  • 707
  • 1
  • 6
  • 17
  • 1
    did you find it helpful ? – Anurag Tripathi Sep 21 '20 at 04:23
  • Edited the answer , let me know if you encounter some error. Also delete your previous comment , it's not a good idea to make your email public . Whatever query you have regarding any coding ask here only . – Anurag Tripathi Sep 22 '20 at 05:34
  • First off I really appreciate your assistance. I've already learned a lot from our conversation. I updated my cloud firestore version to 14.0. Unfortunately I'm getting an error saying that "The name 'DataSnapshot' isn't a type so it can't be used as a type argument." Also I reference this class in other areas of my code will the change from stateless to stateful widget interfere with that? So far I am getting an error but I think thats dude to the data snapshot error. Again thank you for your help. – Emmanuel Thompson Sep 23 '20 at 04:48
  • Sorry again there was an error , It should have been DocumentSnapshot , I dont know how I missed that . A Stateless class has fields which are immutable that means whatever renders on screen will not change . But here you are dynamically deciding which page to show whilst also showing a circular progress indicator , so you have to make this class Stateful . And I dont think going Sateful can affect other parts of the code .It just means whatever you have on screen can change . Also dont forget to reference the first class which extends the SatefulWidget. The second should be marked private . – Anurag Tripathi Sep 23 '20 at 05:20
  • Also dont be afraid to report errors . Thats how this forum works . If you find any error with the answer respond immediately . It is quite possible the peron who has given the answer has not tested the answer by himself . Honestly it is not possible in every case .So there is plenty of room for errors. By responding immediately , you also show that you havent abandoned the question and it encourages the person to rectify his answer. But that also doesnt mean you should not do your own research – Anurag Tripathi Sep 23 '20 at 05:30
  • This platform is pretty great. Thanks man. I think I got it all together. I just have to clean up a few things in some other classes. Have a great day dude. – Emmanuel Thompson Sep 23 '20 at 18:43
  • I'm getting an error telling me that The method '[]' was called on null. Receiver: null Tried calling: []("Counselor") – Emmanuel Thompson Sep 24 '20 at 22:59
  • are you sure the reference to the database is correct?It seems you are getting a DocumentSnapshot which is null . – Anurag Tripathi Sep 25 '20 at 04:00
  • You can share a screenshot of your database , highlighting what you want to get , might able to write right reference . – Anurag Tripathi Sep 25 '20 at 04:13
  • I updated the description and added a picture of the database. – Emmanuel Thompson Sep 26 '20 at 03:43
  • Edited the answer – Anurag Tripathi Sep 26 '20 at 05:46
  • Ok so, I already preform this action in my auth and database file. I set the Counselor value to true depending on whether I press a button or not on sign in. Now I'm just trying to get that counselor value from my database and use it to display a different homepage. I updated my question with my other classes I use. – Emmanuel Thompson Sep 28 '20 at 20:39