Calling a future in a stream with firebase auth and firestore - firebase

I'm trying to have a StreamProvider provide a 'kaizenUser' object throughout the rest of the app. To get this 'KaizenUser' i've pulled the user from firebase authentication. Then using the uid of the firebase auth user, ive used that to access my own user doc form firebase.firestore to create the KaizenUser with the added 'role' information from my firestore user.
Spent a few days wrestling with the code in auth service and i think i'm so close...
Starting with the StreamProvider:
Widget build(BuildContext context) {
return StreamProvider<KaizenUser>.value(
value: AuthService().user,
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: Wrapper(),
),
);
}
Auth Service class.
The get user stream calls userFromFirebaseUser, in which I call getRole to find the document reference of the equivalent user in firestore and the 'role' field.
Then back in userFromFirebaseUser, use that role field to create a complete KaizenUser which will be passed through the stream.
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// auth change user stream
Stream<KaizenUser> get user async* {
yield* _auth
.authStateChanges()
.map((User user) => _userFromFirebaseUser(user));
}
//Create KaizenUser object based on firebase user
KaizenUser _userFromFirebaseUser(User user) {
String role = getRole(user.uid) as String;
KaizenUser kaizenUser =
KaizenUser(uid: user.uid, email: user.email, role: role);
return kaizenUser;
}
// ignore: missing_return
Future<String> getRole(String uid) async {
var doc =
await FirebaseFirestore.instance.collection('users').doc(uid).get();
return doc.data()['role'];
}
The error is:
Type Future is not of type String in type cast.
Whilst writing this out im thinking the problem line of code the cast 'as String'
String role = getRole(user.uid) as String;
Any help is greatly appreciated!

The error is because you are not awaiting for the future result to be returned. Try the following :
String role = await getRole(user.uid):

Related

Registered user not showing in firebase firestore

In short, I want my Authenticated user by Email and Password in firebase firestore,
After successfully register my users only appears in a authentication pannel.
I am currently working with Flutter application where I use firebase_auth: ^1.1.1 package.
So I expect that after
UserCredential userCredential = await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: "abc#example.com",
password: "SuperSecretPassword!"
);
The function called from firebase_auth package which is createUserWithEmailAndPassword
I want this registed user in Firebase Database.
Firebase authentication is used to register a user while firestore is used for storing data. If you want to add the user to firestore also then you need to add the cloud_firestore plugin to your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
cloud_firestore: ^1.0.7
Then you can do:
final firestoreInstance = FirebaseFirestore.instance;
var firebaseUser = FirebaseAuth.instance.currentUser;
firestoreInstance.collection("users").doc(firebaseUser.uid).set(
{
"email" : "abc#example.com",
}).then((_){
print("success!");
});
This will create a collection called users with a document id equal to the authenticated user id, and inside of it you will have the email of the user.
.createUserWithEmailAndPassword creates a user in auth, if you check your auth tab you should see created user. It does not create user in firestore
for this you have to implement yourself .
below is a sample example
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
User user = (await _firebaseAuth.createUserWithEmailAndPassword(
email: email, password: password))
.user;
//if create user succeeds
var user =UserModel(
userID:user.uid,
email:user.email)
FirebaseFirestore.instance
//save user based on their id from auth
.doc("users/${user.uid}")
.set(user.toJson());
Edit
you can create a user model
example:
class UserModel {
final String userID;
final String displayName;
final String email, pushToken;
final String phoneNumber;
final String profilePictureURL, dateJoined;
UserModel({
this.dateJoined,
this.userID,
this.email,
});
Map<String, Object> toJson() {
return {
'userID': userID,
'email': email == null ? '' : email,
'appIdentifier': 'my app',
'dateJoined': DateTime.now(),
};
}
}
....
check how to use json
In addition to the griffins answer, you could use a cloud function to respond to the user creation event (trigger onCreated). Then you can create the users collection, asynchronously, without overloading the flutter client. For example, an index.ts with the createUserInFirestore function:
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
admin.initializeApp();
const db = admin.firestore();
export const createUserInFirestore = functions.auth.user().onCreate(
async (user) => {
return db.collection('users').doc(user.uid).set({
"email" : user.email,
"name" : user.displayName,
});
}
);

Firebase displayName is only shown after Re-login not at the first login

I have 2 methods. One that creates user called signUp and other that adds display name to user profile called updatesUser. During the signUp process, I am calling updateUser method as well.
Once registration is successful, the user is sent to HomeScreen.
However, on signUp completion when user is at HomeScreen, the display name is shown as "null". But then it works when the same user logs out and comes back in.
User object and refs are defined in parent class of these methods:
class FirebaseController extends GetxController {
FirebaseAuth _auth = FirebaseAuth.instance;
Rx<User> _firebaseUser = Rx<User>();
String get user => _firebaseUser.value?.email;
String get userid => _firebaseUser.value?.uid;
String get displayName => _firebaseUser.value?.displayName;
void signUp(String username, String email, String password) async {
CollectionReference dbref = FirebaseFirestore.instance.collection("users");
await _auth
.createUserWithEmailAndPassword(email: email, password: password)
.then((value) {
dbref
.doc(userid)
.set({
"username": username,
"email": email,
"password": password,
"uid": userid,
})
.then((value) => updateUser(username: username))
.then((value) => Get.offAll(HomeScreen()));
}).catchError(
(onError) =>
Get.snackbar("Error while creating account ", onError.message),
);
}
This is my updateUser method that is called during "signUp"
void updateUser({username}) async {
await _auth.currentUser
.updateProfile(displayName: username)
.then((value) => _firebaseUser.value.reload())
.then((value) => Get.snackbar("User Created", "User has been created"))
.catchError((onError) =>
Get.snackbar("Error while Updating DisplayName ", onError.message));
}
What am I doing Wrong?!
It's not about what you're doing wrong. It's about what you're not doing at all.
Updating the user profile does not update the User object for the currently signed in user. If you want to see changes to the new profile, you have to use reload() on that user object.
See also:
What use case has the reload() function of a FirebaseUser in flutter?
changes made to firebase currentUser is not reflected until full reload of the application

Convert FirebaseUser to a customized User class in a Stream

I want to convert a FirebaseUser to a customized class by adding some extra fields in a Flutter project which is using Firebase as backend. Here is my code:
Stream<User> get user {
// return _auth.onAuthStateChanged.map(_fromFirebaseUser);
final theUser = _auth.onAuthStateChanged.map((firebaseUser) {
final result = Firestore.instance.collection("users")
.document(firebaseUser.uid).snapshots().map((snapshot) {
return User(
uid: user.uid,
name: snapshot.data['name'],
email: user.email,
age: snapshot.data['age'],
gender: snapshot.data['gender']
);
}
return result;
});
return theUser;
}
The basic idea is I will get the data from users collection and populate the User model. But I got the following error message:
The argument type 'Stream' can't be assigned to the parameter type 'Stream'.
Need your advice on how to return a Stream<User> instead of Stream<Stream<User>>. Thanks.
In this case, you're using 2 streams: _auth.onAuthStateChanged and Firestore.instance.collection("users").document(firebaseUser.uid).snapshots(). You need to either combine it, or simply asynchronously get the user document from the collection instead of listening for a stream:
Stream<User> get user {
return _auth.onAuthStateChanged.asyncMap((firebaseUser) async {
final snapshot = await Firestore.instance
.collection("users")
.document(firebaseUser.uid)
.get();
return User(
uid: firebaseUser.uid,
name: snapshot.data['name'],
email: firebaseUser.email,
age: snapshot.data['age'],
gender: snapshot.data['gender']
);
});
}
Note the using of asyncMap instead of map to allow asynchronous document fetch.

Passing Firebase current user (as Future) as a parameter to a query in a Stream. Is it possible or is there another way?

I have a Stream building a list from a Firebase collection QuerySnapShot. The query and the stream builder work great, if I don't pass variable data to the query (the 'where' statement). However, what I am trying to do is pass the FirebaseAuth.currentUser as a filter in the where clause of my Stream.
I am sure there is something I am not understanding about making these 2 separate async calls.
Basically I need to get the uid of the currently authenticated user and pass that into the query in my stream.
I am super new to Flutter and am on a rapid fast track to get my chops. Been fully immersed for about a week.
class Booking {
final DateTime startTime;
final DateTime endTime;
final String name;
final String bookingId;
final String truckID;
Booking({ this.bookingId, this.truckID, this.startTime, this.endTime, this.name });
}
// build the booking list from the QuerySnapShot
List<Booking> _bookingListFromSnapshot(QuerySnapshot snapshot) {
return snapshot.documents.map((doc) {
return Booking(
bookingId: doc.documentID ?? '',
name: doc.data['name'] ?? '',
startTime: doc.data['startTime'].toDate() ?? '',
endTime: doc.data['endTime'].toDate() ?? '',
truckID: doc.data['truckID']
);
}).toList();
}
//asynchronously get the uid of the currentuser from FirebaseAuth
Future<String> inputData() async {
final FirebaseUser _aUser = await FirebaseAuth.instance.currentUser();
final String _uid = _aUser.uid.toString();
return _uid;
}
Here is where I am trying to pass the current user into the Stream
//get user specific booking stream
Stream<List<Booking>> get bookings {
final _myUserId = inputData();
return bookingCollection
.where("truckID", isEqualTo: _myUserId) //Instance of 'Future<String>'...needs to be the uid of the current user.
.snapshots()
.map(_bookingListFromSnapshot);
}
// the widget consuming the list
class _BookingListState extends State<BookingList> {
#override
Widget build(BuildContext context) {
final bookings = Provider.of<List<Booking>>(context) ?? [];
return ListView.builder(
itemCount: bookings.length,
itemBuilder: (context,index){
return BookingTile(booking: bookings[index]);
},
);
}
}
EDIT to include the Stream usage for feedback (after wrapping the Stream in a Future as suggested)
In my home.dart file I listen for the Stream<List<Booking>>> so I can build the list of bookings that are displayed on that page. In this next block I now get an error that I cannot assign the parameter type Stream<List<Booking>> to the argument type Future<Stream<List<Booking>>>. The compiler suggests changing the parameter type or casting the argument to <Stream<list<Booking>>
The full compile message
lib/screens/home/home.dart:38:32: Error: The argument type 'Future<Stream<List<Booking>>>' can't be assigned to the parameter type 'Stream<List<Booking>>'.
- 'Future' is from 'dart:async'.
- 'Stream' is from 'dart:async'.
- 'List' is from 'dart:core'.
- 'Booking' is from 'package:models/booking.dart' ('lib/models/booking.dart').
Try changing the type of the parameter, or casting the argument to 'Stream<List<Booking>>'.
value: DatabaseService().bookings,
home.dart
return StreamProvider<List<Booking>>.value(
value: DatabaseService().bookings,
child: Scaffold( ... ) //Scaffold
); //StreamProvider.value
I have tried changing either the parameter value or the argument DatabaseService().value to the suggested types...and I have failed :)
modified bookings getter after I changed it based on feedback
//get user specific booking stream
Future<Stream<List<Booking>>> get bookings async {
final _myUserId = await inputData();
print(_myUserId);
return bookingCollection
.where("truckID", isEqualTo: _myUserId) //here is where I want to pass the currentUser
.snapshots()
.map(_bookingListFromSnapshot);
}
Yes, you can use and await futures only inside async function. So first change your bookings getter as follows.
//get user specific booking stream
Future<Stream<List<Booking>>> get bookings {
final _myUserId = await inputData();
return bookingCollection
.where("truckID", isEqualTo: _myUserId) //Instance of 'Future<String>'...needs to be the uid of the current user.
.snapshots()
.map(_bookingListFromSnapshot);
}
So, where you are providing this stream, you need to provide Future then only you can get stream from Future.

How can I add some data to firestore as per user logged in, as well as how can I create the profile of the user?

I'm developing a restaurant application with flutter, I want to use firestore as my database and I've already authenticated with firebase with the help of Andrea Bizzotto's YouTube tutorial of authentication but I'm not able to understand any tutorial or either not able to integerate into the login/signup page for creating a user info in cloud firestore, can someone give me a way to do this, just give a basic introduction or direction to find a way to complete it. Thanks in advance.
after you sign up the user with firebase auth if it's successful you should then run this method to update his data.
Future<dynamic> updateUserData(FirebaseUser user, String photoUrl, String displayName) async {
final Firestore _db = Firestore.instance;
DocumentSnapshot snapshot =
await _db.collection('users').document(user.uid).get();
DocumentReference ref = _db.collection('users').document(user.uid);
if (snapshot.exists) {
return ref;
} else {
return Firestore.instance.runTransaction((Transaction tx) async {
return tx.set(
ref,
{
'uid': user.uid,
'email': user.email,
'name': displayName,
'photo': photoUrl,
'lastSeen': DateTime.now()
},
);
}).then((val) => val.length);
}
}

Resources