Convert FirebaseUser to a customized User class in a Stream - firebase

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.

Related

firebase is not creating the user (user.uid = null)

I am trying to implemente facebook signin in flutter, however, firebase does not create a 'uid'. Doesn't the firebase create a uid automatically?
it returns:
The getter 'uid' was called on null.
Receiver: null
Tried calling: uid
below is the sign in method:
Future<UserCredential> signInWithFacebook(BuildContext context) async {
final LoginResult result = await FacebookAuth.instance.login();
if(result.status == LoginStatus.success) {
final OAuthCredential credential = FacebookAuthProvider.credential(result.accessToken.token);
return await FirebaseAuth.instance.signInWithCredential(credential)
.then((user) async {
final graphResponse = await http.get(Uri.parse(
'https://graph.facebook.com/v2.12/me?
fields=name,picture,email&access_token=${result
.accessToken.token}'));
final Map profile = jsonDecode(graphResponse.body);
if (profile != null){
authService.createUser(name: name, email: email, dob: dob, sex: sex);
}
return user;
});
}
Navigator.pushReplacement(context, MaterialPageRoute(
builder: (context) => Profile()));
return null;
}
The sign in method returns a facebook alert dialog requesting the permission to share email, when press continue red screen with the error appears. why is the firestore not creating the user? Thanks! I am not familiar with the system and just learning.
create user method in authServices:
Future<bool> createUser(
{String name,
User user,
String email,
String password,
String phone,
String sex,
String dob}) async {
var res = await firebaseAuth.createUserWithEmailAndPassword(
email: '$email',
password: '$password',
);
if ((res.user != null)) {
await saveUserToFirestore(name, res.user, email, dob, phone, sex);
return true;
} else {
return false;
}
}
As far as I can understand your code you first login the user with Facebook and then again create a new user with createUserWithEmailAndPassword. If you use the same email for both the second one will fail and give you null.
To track the auth state for all providers use the onAuthStateChanged listener:
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
var uid = user.uid;
// ...
} else {
// User is signed out
// ...
}
});
More about it here.

Running Async Code in factory Function in Flutter / Dart

I have this function using factory, (which to be honest I'm not sure why is needed, I just got it from a Tutorial, but is working the rest of the code).
This is the Function in question:
factory Listing.fromJason(Map<String, dynamic> data, String id) {
List<String> images = [];
data['photos'].forEach((photo) {
images.add(photo);
});
return Listing(
id: id,
photos: images,
price: double.parse(data['price'].toString()),
status: data['status'],
street: data['street'],
street2: data['street2'],
city: data['city'],
state: data['state'],
zipCode: int.parse(data['zipCode'].toString()),
bedRooms: data['bedRooms'],
bathRooms: data['bathRooms'],
lotSize: data['lotSize'],
schoolDistric: data['schoolDistric'],
taxes: double.parse(data['taxes'].toString()),
homeFeatures: data['homeFeatures'],
floorPlans: data['floorPlans'],
propertySurvey: data['propertySurvey'],
yearBuilt: data['yearBuilt'],
listingAgentName: data['listingAgentName'],
listingAgentEmail: data['listingAgentEmail'],
listingAgentPhone: data['listingAgentPhone'],
dayStore: DateTime.parse(data['dayStore'].toDate().toString()),
downPayment: data['downPayment'],
county: data['county'],
url: data['url'],
listingType: data['listingType'],
name: data['name'],
username: data['username'],
email: data['email'],
imageUrl: data['image_url'],
//isFavorite: favStat,
);
}
The Problem that I'm having is that I need to call inside of it this function to evaluate the Favorite Status:
Future<bool> isItFav(docId) async {
final user = FirebaseAuth.instance.currentUser;
final uid = user.uid;
DocumentSnapshot favoriteSnapshot = await FirebaseFirestore.instance
.doc('userfavorites/$uid/favorites/$docId')
.get();
bool result = favoriteSnapshot.exists;
return result;
}
The problem is that inside that function the one with the factory doesn't allow me to leave it as async or to put an await.
And I need to evaluate with the value of id (which I get as a parameter on top) if the document for that collection (the one on the isItFav function) exists and with that bool add it to the Listing object that I'm returning.
Any Ideas on what I can do.
Just use a static method instead of a named factory constructor. A factory constructor offers no significant advantages in this case.

Calling a future in a stream with firebase auth and firestore

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):

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.

Is there a way to create Auth object and use that UID to create a doc with GeoFirestore

I am trying to create an Auth object in firebase that returns the User UID. I want to be able to create a document in my collection with that particuar UID but apparently geofirestore doesn't have a feature to add a document with a particular ID.
const storesCollection = geoFirestore.collection("retailers");
export const firstTimeStartCreateRetailer = ( email, password) => async dispatch => {
try {
const { user } = await auth.createUserWithEmailAndPassword(email, password);
await storesCollection.doc(user.uid).add({
coordinates: new firebase.firestore.GeoPoint(33.704381, 72.978839),
name: 'Freshlee',
location: 'F-11',
city: 'Islamabad',
inventory: [],
rating: 5,
categories: []
})
dispatch({ type: LOGIN, payload: { ...user } });
} catch (error) {
console.log(error)
}
};
this code is rejected because geoFirestore doesn't have the .doc(id) referencing feature. How can I achieve this.
You need to do
await storesCollection.doc(user.uid).set({...})
using the set() method. As a matter of fact, there is no add() method for a GeoDocumentReference and storesCollection.doc(user.uid) is a GeoDocumentReference.
The add() method is a method of a GeoCollectionReference.
Because storesCollection is a GeoCollectionReference, the API is not always the same as native Firestore references.
In your particular case, you get the document you want to write to using doc(id), but instead of using add(...) which is used on collections, you need to use set(...) instead to create/overwrite the data for that particular document.
await storesCollection.doc(user.uid).set({
coordinates: new firebase.firestore.GeoPoint(33.704381, 72.978839),
name: 'Freshlee',
location: 'F-11',
city: 'Islamabad',
inventory: [],
rating: 5,
categories: []
});

Resources