Flutter passing Firestore UID into repository - firebase

For my project, I am using firestore to get the data per user. Therefore I created the following structure in Firestore.
The data is stored in:
Collection: users > Document: UID (variable) > Collection: reminders
In order to get the data from Firestore I need to pass the UID into the repository. My reminder repository doesn't have this data standard therefor I get it via a call to Firebase.
Let me show the code to make it more clear:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:diabetes/src/model/models.dart';
import 'package:diabetes/src/entities/entities.dart';
import 'package:diabetes/src/repositories/repositories.dart';
class FirebaseRemindersRepository implements RemindersRepository {
final reminderCollection = Firestore.instance.collection("users");
#override
Future<void> addNewReminder(Reminder reminder) async {
try {
final user = await FirebaseAuth.instance.currentUser();
final uid = user.uid;
return reminderCollection
.document(uid)
.collection("reminders")
.add(reminder.toEntity().toDocument());
} catch (error) {
print(error);
}
}
#override
Future<void> deleteReminder(Reminder reminder) async {
try {
final user = await FirebaseAuth.instance.currentUser();
final uid = user.uid;
return reminderCollection
.document(uid)
.collection("reminders")
.document(reminder.id)
.delete();
} catch (error) {
print(error);
}
}
#override
Stream<List<Reminder>> reminders() {
return reminderCollection.snapshots().map((snapshot) {
return snapshot.documents
.map((doc) => Reminder.fromEntity(ReminderEntity.fromSnapshot(doc)))
.toList();
});
}
#override
Future<void> updateReminder(Reminder update) async {
try {
final user = await FirebaseAuth.instance.currentUser();
final uid = user.uid;
return reminderCollection
.document(uid)
.collection("reminders")
.document(update.id)
.updateData(update.toEntity().toDocument());
} catch (error) {
print(error);
}
}
}
As you can see this makes me repeat this part a lot:
final user = await FirebaseAuth.instance.currentUser();
final uid = user.uid;
Also, I am not able to do the same here:
Stream<List<Reminder>> reminders() {

Related

The getter 'uid' not defined

i'm trying to create a food track app on android studio, it's my first time and i'm working with firebase_auth 3.3.12. my code in the aut.dart is:
import 'package:firebase_auth/firebase_auth.dart';
import 'package:my_firstapp/models/user_model.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
AuthService();
// create user object based on FirebaseUser.
UserModel _userFromUser(User) {
return user != null ? UserModel(uid: user.uid) : null;
}
// auth change user stream
Stream<UserModel> get user {
return _auth.authStateChanges()
.map(_userFromUser);
}
Future<UserModel> getUser() async {
User user = await _auth.currentUser();
return _userFromUser(user);
}
// sign in with email and password
Future signInWithEmailAndPassword(String email, String password) async {
try {
UserCredential result = await _auth.signInWithEmailAndPassword(email: email, password: password);
User user = result.user;
return _userFromUser(user);
} catch(e) {
print(e.toString());
return null;
}
}
// sign up with email and password
Future registerWithEmailAndPassword(String email, String password) async {
try {
UserCredential result = await _auth.createUserWithEmailAndPassword(email: email, password: password);
User user = result.user;
// create a new user document in database
return _userFromUser(user);
} catch(e) {
print(e.toString());
return null;
}
}
// sign out
Future signOut() async {
try {
return await _auth.signOut();
} catch(e){
print(e.toString());
return null;
}
}
}
However i'm getting 2 errors:
-The getter 'uid' isn't defined for the type 'Stream';
-The expression "await _auth.currentUser()" doesn't evaluate to a function, so it can't be invoked.
How can i rewrite the code? thanks
The _auth.currentUser is not a function (it used to be, but changed about a year ago), but rather a property. It also isn't asynchronous, so you don't need await nor to return a Future.
So:
UserModel getUser() {
User user = _auth.currentUser;
return _userFromUser(user);
}
In this code, your argument is capitalised ('User') but in the code block you write 'user'.
UserModel _userFromUser(User) {
return user != null ? UserModel(uid: user.uid) : null;
}
Furthermore, for _auth.currentUser(), you do not need to use await as it does not return a future.

Firebase _upload writes random downloadURL

Below is a simple firebase image uploader. The problem is that it sometimes uses another image's downloadURL as the value when it writes to Firestore. It uploads my image to cloud storage without a problem but then when it goes to write the location to firestore, it often uses the URL of another image. The full code is below but I have omitted the UI. How do I ensure that it writes the correct URL to firestore?
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:path/path.dart' as path;
import 'package:image_picker/image_picker.dart';
class ImagePicky2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
// Remove the debug banner
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.green),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
FirebaseStorage storage = FirebaseStorage.instance;
double? lat, lng;
File? file;
String? name, detail, pathImage, dateString;
// Select an image from the gallery or take a picture with the camera
// Then upload to Firebase Storage
Future<XFile?> _upload(String inputSource) async {
FirebaseAuth auth = FirebaseAuth.instance;
User firebaseUser = auth.currentUser!;
final picker = ImagePicker();
try {
final pickedImage = await picker.pickImage(
source: inputSource == 'camera'
? ImageSource.camera
: ImageSource.gallery,
imageQuality: 25,
maxWidth: 1920);
final String fileName = path.basename(pickedImage!.path);
File imageFile = File(pickedImage.path);
try {
// Uploading the selected image with some custom meta data
await storage.ref(fileName).putFile(
imageFile,
SettableMetadata(customMetadata: {
'uploaded_by': firebaseUser.displayName!,
'description': 'Some description...'
}));
// Refresh the UI
setState(() {});
} on FirebaseException catch (error) {
print(error);
}
} catch (err) {
print(err);
}
photoUploadFirestoreDetails();
}
// Retriew the uploaded images
// This function is called when the app launches for the first time or when an image is uploaded or deleted
Future<List<Map<String, dynamic>>> _loadImages() async {
FirebaseAuth auth = FirebaseAuth.instance;
User firebaseUser = auth.currentUser!;
List<Map<String, dynamic>> files = [];
final ListResult result = await storage.ref().list();
final List<Reference> allFiles = result.items;
await Future.forEach<Reference>(allFiles, (file) async {
final String fileUrl = await file.getDownloadURL();
pathImage = await file.getDownloadURL();
final FullMetadata fileMeta = await file.getMetadata();
files.add({
"url": fileUrl,
"path": file.fullPath,
"uploaded_by": fileMeta.customMetadata?['uploaded_by'] ?? firebaseUser.displayName,
"description":
fileMeta.customMetadata?['description'] ?? 'No description'
});
});
return files;
}
Future<Null> photoUploadFirestoreDetails() async {
Firebase.initializeApp();
Map<String, dynamic> map = Map();
map['PathImage'] = pathImage;
FirebaseFirestore firestore = FirebaseFirestore.instance;
CollectionReference collectionReference =
firestore.collection('MarkerCollect');
await collectionReference.doc().set(map).then((
value) {
});
}
}```
The code is uploading random download urls to Firestore because you're getting the image path from the _loadImages method which loads up the files on storage instead of using the download url of the just uploaded file.
This is the problematic code:
Future<Null> photoUploadFirestoreDetails() async {
...
map['PathImage'] = pathImage;
...
}
Solution:
You can fix this by retrieving the download url just after the upload and passing it to the photoUploadFirestoreDetails method to be used in the Firestore upload.
You should also put the photoUploadFirestoreDetails in the try-catch.
Checkout the updated code below:
// _upload method
Future<XFile?> _upload(String inputSource) async {
FirebaseAuth auth = FirebaseAuth.instance;
User firebaseUser = auth.currentUser!;
final picker = ImagePicker();
try {
final pickedImage = await picker.pickImage(
source: inputSource == 'camera'
? ImageSource.camera
: ImageSource.gallery,
imageQuality: 25,
maxWidth: 1920);
final String fileName = path.basename(pickedImage!.path);
File imageFile = File(pickedImage.path);
try {
// Uploading the selected image with some custom meta data
final Reference storageReference = storage.ref(fileName);
await storageReference.putFile(
imageFile,
SettableMetadata(customMetadata: {
'uploaded_by': firebaseUser.displayName!,
'description': 'Some description...'
}));
final String downloadUrl = await storageReference.getDownloadURL();
// Refresh the UI
setState(() {});
await photoUploadFirestoreDetails(downloadUrl: downloadUrl);
} on FirebaseException catch (error) {
print(error);
}
} catch (err) {
print(err);
}
}
// photoUploadFirestoreDetails method
Future<Null> photoUploadFirestoreDetails({#required String downloadUrl}) async {
Firebase.initializeApp();
Map<String, dynamic> map = Map();
map['PathImage'] = downloadUrl;
FirebaseFirestore firestore = FirebaseFirestore.instance;
CollectionReference collectionReference =
firestore.collection('MarkerCollect');
var value = await collectionReference.doc().set(map);
}
Try this function to upload image to fire-storage and get Url
Future<String?> uploadAndGetUrl(File file) async {
try {
final Reference ref = FirebaseStorage.instance
.ref()
.child('profilePhoto')
.child(DateTime.now().microsecondsSinceEpoch.toString());
UploadTask uploadTask = ref.putFile(file);
await uploadTask.whenComplete(() {});
String url = await ref.getDownloadURL();
return url;
} catch (e) {
print('Firebase Storage Error is : $e');
return null;
}
}
OR you can just upload an image and get the image URL later.
Your upload image function looks okay. the name should be unique. otherwise, it returns a different image url.
Future<String> getUrl(String imageName) async {
try {
Reference storageRef = FirebaseStorage.instance.ref().child('profilePhoto/$logo');
String url = await storageRef.getDownloadURL();
return url;
} catch (e) {
return null;
}
}

Endless loop in firestore

I'm trying to update my documents in firestore, so when I'm trying to update it keeps updating without stopping. The first time it updates using the data from the signup dart file, then the second time it updates using the data from another dart file.
Here is the code for the signup:
FirebaseAuth auth = FirebaseAuth.instance;
await auth.createUserWithEmailAndPassword(
email: emailController.text, password: passwordController.text)
.then((value) => {
Navigator.pushNamed(context, 'DialogFlow'),
user=auth.currentUser,
user.sendEmailVerification(),
DatabaseService(uid:user.uid).UpdateUserData("", emailController.text, ChatScreenState().mess)
Here is the code for the other dart file:
#override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToEnd());
FirebaseAuth auth = FirebaseAuth.instance;
user=auth.currentUser;
DatabaseService db = DatabaseService(uid: user.uid);
return StreamBuilder(
stream: FirebaseFirestore.instance.collection("users").doc(user.uid).snapshots(),
builder: (context , snapshot){
print("====================================");
print(snapshot.data);
print("====================================");
if (snapshot.data != null) {
this.userTestMessage = "";
shhh = pressed ? true : false;
flag = true;
print(Retrieved_messages);
if (Retrieved_messages==false) {
this.messsages = snapshot.data['messsages'];
Retrieved_messages=true;
}
db.UpdateUserData(
user.displayName, user.email, this.messsages);
print(mess);
print(Retrieved_messages);
print("==============================");
print(snapshot.data);
print("==============================");
}
if (db.getUserMessages() == null) {
if (user != null) {
db.UpdateUserData(
user.displayName, user.email, this.messsages);
}
}
And the code for the database which sets and updates the documents is:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:satoshi/models/Userdata.dart';
import 'package:satoshi/widgets/dialog_flow.dart';
class DatabaseService {
//collection reference
final String uid;
List messsages=[];
DatabaseService({this.uid, this.messsages});
final CollectionReference userCollection = FirebaseFirestore.instance
.collection('users');
SetUserData(String Username, String Email,
List messsages) async
{ try {
return await FirebaseFirestore.instance.collection("users").doc(uid).set({
'Username': Username,
'Email': Email,
'messsages': messsages,
}
);
}catch(e){
print(e+" this is the error");
}
}
UpdateUserData(String Username, String Email,
List messsages) async
{ try {
return await FirebaseFirestore.instance.collection("users").doc(uid).update({
'Username': Username,
'Email': Email,
'messsages': messsages,
}
);
}catch(e){
print(e+" this is the error");
}
}
Future getUserMessages() async
{
DocumentSnapshot UserDetail = await userCollection.doc(uid).get();
var msg = UserDetail.data()['messsages'];
return await msg;
}
Stream<QuerySnapshot> get users {
return userCollection.snapshots();
}
Userdata userDataFromSnapshot(DocumentSnapshot snapshot) {
return Userdata(uid: uid,
name: snapshot.get('Username'),
email: snapshot.get('Email'),
messsages: snapshot.get('messsages'),
);
}
Stream<Userdata> get userData {
return userCollection.doc(uid).snapshots().asyncMap(userDataFromSnapshot);
}
}
Note: it keeps adding the data in the signup code, then adds the data in the other dart file, which results in an endless loop, also the snapshot isn't updating, it remains the same data as the signup
You are calling the Update function inside the stream builder so what it basically does is once the update function is called firebase gets notified of the document change and rebuilds the widget so again the update function is called and it turns into an infinite loop. what you can do is add any condition such that it won't get called again once the data is updated.
Example
if (<Somecondition>){
db.UpdateUserData(
user.displayName, user.email, this.messsages);
}

Getting Boolean State from Cloud_Firestore in dart

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;
}
}
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.

Flutter: Instance member 'signInWithGoogle' can't be accessed using static access. (static_access_to_instance_member at )

I try to lint for my Flutter project, I have a class API to log in and log out google account, Linter prefers to remove static before these methods (login with Google and sign out). I cannot call these functions in view. Here my code:
API.dart
class FBApi {
FBApi(this.firebaseUser);
...
Future<FBApi> signInWithGoogle() async {
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
...
}
Future<void> signOut() async {
await _auth.signOut().then((_) {
print('***** log out...what the hell?');
_googleSignIn.disconnect();
_googleSignIn.signOut();
// Navigator.of(context).pushNamedAndRemoveUntil("/login", ModalRoute.withName("/home"));
});
}
}
Login.dart error above
Future<bool> _loginUser() async {
final FBApi api = await FBApi.signInWithGoogle();---> error
if (api != null) {
return true;
} else {
return false;
}
}
Logout.dart
Future<void> _signOut() async {
try {
await FBApi.signOut();
} catch (e) {
print(e);
}
}
await FBApi.signInWithGoogle();---> error
should be
await FBApi().signInWithGoogle();
You first need to create an instance () to call an instance method.
Alternatively you can change
Future<FBApi> signInWithGoogle() async {
to
static Future<FBApi> signInWithGoogle() async {
to make signInWithGoogle available without creating an instance first.
I don't know what the intention actually is.

Resources