Cloud Firestore not saving data flutter - flutter

I'm learning Flutter and as an exercise I'm interfacing with Firebase (I'm also new to this - my first encounter).
In the exercise when we register a new user we create a document for the newly registered user with default values.
A new user is getting registered but neither collection nor document is getting created.
I had created the Cloud Firestore is Test Mode. I've attached the rules bellow.
Rules :
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if
request.time < timestamp.date(2021, 3, 4);
}
}
}
auth.dart
//Resgister using email and password
Future registerWithEmailPassword(
{#required String email, #required String password}) async {
try {
Firebase.UserCredential resut = await _auth
.createUserWithEmailAndPassword(email: email, password: password);
Firebase.User user = resut.user;
//create a new document for user with uid
await DatabaseService(uid: user.uid).updateUserData(
sugars: '0',
name: 'new crew member',
strength: 100,
);
return _userFromFirebase(user);
} catch (e) {
//print(e.toString());
return e.toString();
}
}
database.dart
import 'package:cloud_firestore/cloud_firestore.dart';
class DatabaseService { final String uid; DatabaseService({this.uid});
//colection reference
final CollectionReference brewCollection =
FirebaseFirestore.instance.collection('brews');
Future updateUserData({String sugars, String name, int strength}) async {
return await brewCollection
.doc(uid)
.set({'sugars': sugars, 'name': name, 'strength': strength}); } }
}
I added a break-point and checked the data in updateUserData, the data is proper.

Their was an issue with the Firebase plugin.
MissingPluginException(No implementation found for method DocumentReference#set on channel plugins.flutter.io/firebase_firestore)
I updated by pubspec.yaml to have the latest version file then run the following commands
flutter clean
flutter packages get

Related

Flutter - Local module descriptor class for com.google.android.gms.providerinstaller.dynamite not found

Flutter is not writing data to my firebase collection and has errors in the debug.
Steps to reproduce
https://www.youtube.com/watch?v=EA7973HI93E&list=PL4cUxeGkcC9j--TKIdkb3ISfRbJeJYQwC&index=18
Steps to reproduce the behavior:
Run the app,
register a user.
Expected behavior:
I expected that the user will be made and data to the collection added
the output
Ignoring header X-Firebase-Locale because its value was null. D/FirebaseAuth(17181): Notifying id token listeners about user ( fTnXUNwmPUajE9ybOeP3GLW0e392 ). D/FirebaseAuth(17181): Notifying auth state listeners about user ( fTnXUNwmPUajE9ybOeP3GLW0e392 ). W/DynamiteModule(17181): Local module descriptor class for com.google.android.gms.providerinstaller.dynamite not found. I/DynamiteModule(17181): Considering local module com.google.android.gms.providerinstaller.dynamite:0 and remote module com.google.android.gms.providerinstaller.dynamite:0 W/ProviderInstaller(17181): Failed to load providerinstaller module: No acceptable module com.google.android.gms.providerinstaller.dynamite found. Local version is 0 and remote version is 0. W/Firestore(17181): (24.4.0) [WriteStream]: (e9a00e8) Stream closed with status: Status{code=UNAVAILABLE, description=Channel shutdownNow invoked, cause=null}. W/DynamiteModule(17181): Local module descriptor class for com.google.android.gms.providerinstaller.dynamite not found.
my auth.dart code:
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:my_app/models/user.dart';
import 'package:my_app/services/database.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// create MyUser object based on User
MyUser? _userFromFirebaseUser(User? user) {
return user != null ? MyUser(uid: user.uid) : null;
}
// auth change user stream
Stream<MyUser?> get user {
return _auth.authStateChanges().map(_userFromFirebaseUser);
}
//sign in anonymously
Future signInAnon() async {
try {
UserCredential result = await _auth.signInAnonymously();
User? user = result.user;
return _userFromFirebaseUser(user);
} catch (e) {
print(e.toString());
return null;
}
}
// 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 _userFromFirebaseUser(user);
} catch (e) {
print(e.toString());
return null;
}
}
// register 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 new document for the user with uid
await DatabaseService(uid: user?.uid)
.updateUserData('How do you feel', 'I am mad', 'angry');
return _userFromFirebaseUser(user);
} on FirebaseAuthException catch (e) {
print(e.toString());
return null;
}
}
// sign out
Future signOut() async {
try {
return await _auth.signOut();
} catch (e) {
print(e.toString());
return null;
}
}
}
My database.dart
import 'package:cloud_firestore/cloud_firestore.dart';
class DatabaseService {
// collection reference
final String? uid;
DatabaseService({this.uid});
final CollectionReference pollearnCollection =
FirebaseFirestore.instance.collection('Pollearn');
Future updateUserData(String question, String answer, String emotion) async {
Map<String, String> data = {
'question': question,
'answer': answer,
'emotion': emotion,
};
return await pollearnCollection.doc(uid).set(data);
}
}
I already tried the following:
turn the emulator on and off.
tried different emulator
updated google play services
turnt bluetooth off
turnt internet off and on
enabled sign in with mail and password
adding
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
I hope you guys can help me. Thanks in advance!

Flutter Riverpod is returning only one item when it should return multiple

I'm using Riverpod with Flutter and Firebase as database. In my database I have two collections; Users and Sites.
What I'm trying to do:
Finding a single document from the Sites-collection. This document has an
array containing user-ids.
For each of the user-ids found, find the corrosponding user-documents from the User-collection
return a stream of users (with their user information such as name, email etc).
What the result is:
The firebase database contains two users, however, only one is displayed in my widget.
The question:
What am I doing wrong in the code? I suspect Iterable<UserInfoModel>? users; is being overwritten instead of storing all users found.
Sites? site;
Iterable<UserInfoModel>? users;
final workersProvider = StreamProvider
.family
.autoDispose<WorkersDisplay, String>((ref, String siteId) {
final usersController = StreamController<WorkersDisplay>();
void notify(){
final localSite = site;
if(localSite == null){ return; }
final outputUsers = (users ?? []);
print("out: $outputUsers");
final result = WorkersDisplay(site: localSite, workers: outputUsers);
usersController.sink.add(result);
}
// watch changes to the site
final siteSub = FirebaseFirestore
.instance
.collection(FirebaseCollectionName.sites)
.where(FieldPath.documentId, isEqualTo: siteId)
.snapshots()
.listen((snapshot){
if(snapshot.docs.isEmpty){
site = null;
notify();
return;
}
final doc = snapshot.docs.first;
if(doc.metadata.hasPendingWrites){
return;
}
site = Sites(
siteId: doc.id,
json: doc.data()
);
notify();
List? workers = site?.workers;
workers?.forEach((user) {
FirebaseFirestore
.instance
.collection(FirebaseCollectionName.users)
.where(FieldPath.documentId, isEqualTo: user)
.snapshots()
.listen((snapshot2) {
users = snapshot2.docs
.where((docs) => !docs.metadata.hasPendingWrites)
.map((docs) {
String displayName = docs.data()['display_name'];
String uid = docs.data()['uid'];
String email = docs.data()['email'];
return UserInfoModel(
userId: uid,
displayName: displayName,
email: email);
}
);
notify();
});
});
notify();
});
ref.onDispose(() {
//usersSub.cancel();
siteSub.cancel();
usersController.close();
});
return usersController.stream;
});
Output:
flutter: out: []
flutter: out: []
flutter: out: ({uid: O0gKwVhjqtQjbCWu8Gn71PoYqxP2, display_name: Bob, email: bob#bob.com})
flutter: out: ({uid: dTCuWlpUkTWlGzip6YNs, display_name: Test User, email: test#test.com})

show display name after signUp

I have a flutter firebase createUserWithEmailAndPassword function with the displayName update.
display name prints normally at the moment of fb user creation.
After signup MainPage loads with the users email and displayName. But displayName returns null value error. If I delete displayName from the MainPage - all works fine.
If I reload app, it works fine.
When I login, it works fine.
It can't pass the displayName at the moment of signup only.
Where I am wrong?
class AuthServiceProvider extends ChangeNotifier {
final auth.FirebaseAuth _firebaseAuth = auth.FirebaseAuth.instance;
final googleSingIn = GoogleSignIn();
UserModel? _userFromFirebase(auth.User? user) {
if (user == null) {
return null;
}
return UserModel(
user.displayName,
user.uid,
user.email,
);
}
Stream<UserModel?>? get user {
return _firebaseAuth.authStateChanges().map(_userFromFirebase);
}
Future<UserModel?> createUserWithEmailAndPassword(
String name,
String email,
String password,
) async {
try {
final userCred = await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
auth.User? firebaseUser = _firebaseAuth.currentUser;
if (firebaseUser != null) {
await firebaseUser.updateDisplayName(name);
await firebaseUser.reload();
firebaseUser = _firebaseAuth.currentUser;
}
print('FIREBASE USER IS $firebaseUser');
return _userFromFirebase(firebaseUser);
} catch (e) {
print(e.toString());
return null;
}
}
}
If your class were to extend either StatelessWidget or StatefulWidget, then all you'd have to do is to pass the data (displayName) between the screens.
This is not an answer but a suggestion:
You should try changing the ChangeNotifier to a StatefulWidget
and pass the data between screens...
You could also setup an
Authentication class that will hold all these Future methods so that
these calls can be reusable in your code. With this method, all you have to do is to call the specific function and give its required parameters.
As usually the solution is very simple if you think a little bit.
As all this is through the firebase auth, at the main page loading I just grab the firebase user with its display name that is saved in FB both for GoogleSignIn and createUserWithEmailAndPassword (required at registration)
import 'package:firebase_auth/firebase_auth.dart' as auth;
final auth.FirebaseAuth _firebaseAuth = auth.FirebaseAuth.instance;
final String firebaseUser =
_firebaseAuth.currentUser!.displayName ?? 'Unknown user';

Firebase documentation for flutter does not work for deleting user

The following documentation on deleting a user does not work:
try {
await FirebaseAuth.instance.currentUser.delete();
} catch on FirebaseAuthException (e) {
if (e.code == 'requires-recent-login') {
print('The user must reauthenticate before this operation can be executed.');
}
}
"delete()" is not a function recognized by Flutter. "FirebaseAuthException" is also not recognized by Flutter.
How do I delete a user? Where do I find this information?
Using flutter, if you want to delete firebase accounts together with the associated firestore user collection document, the following method works fine. (documents in user collection named by the firebase uid).
Database Class
class DatabaseService {
final String uid;
DatabaseService({this.uid});
final CollectionReference userCollection =
Firestore.instance.collection('users');
Future deleteuser() {
return userCollection.document(uid).delete();
}
}
Use Firebase version 0.15.0 or above otherwise, Firebase reauthenticateWithCredential() method throw an error like { noSuchMethod: was called on null }.
Authentication Class
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
Future deleteUser(String email, String password) async {
try {
FirebaseUser user = await _auth.currentUser();
AuthCredential credentials =
EmailAuthProvider.getCredential(email: email, password: password);
print(user);
AuthResult result = await user.reauthenticateWithCredential(credentials);
await DatabaseService(uid: result.user.uid)
.deleteuser(); // called from database class
await result.user.delete();
return true;
} catch (e) {
print(e.toString());
return null;
}
}
}
Then use the following code inside the clickable event of a flutter widget tree to achieve the goal:
onTap: () async {
await AuthService().deleteUser(email, password);
}

Flutter Firestore Authentication

I have a Flutter project that's using the cloud_firestore plugin for data access. Once a user authenticates to the application, what do I need to do to set that as the authentication used by the Firestore client? For example, I just have these basic rules enabled:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
match /users/{userId}/{document=**} {
allow read, update, delete, create: if request.auth.uid == userId;
}
match /ingredients/* {
allow read, create: if request.auth.uid != null;
}
match /units/* {
allow read, create: if request.auth.uid != null;
}
match /recipes/* {
allow read, create, update: if request.auth.uid != null;
}
}
}
As soon as I enabled those rules, every request from my Flutter app started failing. If I test the Firestore rules with the little "simulator" they have, they work as expected, so the authentication does not appear to be getting set correctly from the Flutter app side.
EDIT: Adding some code samples.
I have authentication code that uses Google Auth, so when the user logs in it looks like this:
class Auth implements AuthService {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
GoogleSignIn _googleSignIn = GoogleSignIn(
scopes: [
'email',
'https://www.googleapis.com/auth/contacts.readonly',
],
);
Future<String> signInWithGoogle() async {
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth = await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.getCredential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
final FirebaseUser user = await _firebaseAuth.signInWithCredential(credential);
return user.uid;
}
I've verified that the user is being authenticated properly.
Then, when accessing Firestore, something like:
DocumentSnapshot userSnapshot = await Firestore.instance
.collection('users')
.document(userId)
.collection('shoppingLists')
.document(listName)
.get();
I've followed all of the guides to add Firebase and Firestore to my app, and I didn't see anything specific about setting the currently authenticated user as the user that's making the Firestore requests, so I feel like I'm missing something there. Is there something I'm supposed to be doing when making the Firestore queries to pass in the current user?
The answer is to not user Firestore.instance as this gives you an instance disconnected from your Firebase app;
instead use create you Firebase app providing all the keys, then authenticate against that app, and then create a Firestore instance against that app. There's no way to explicitly pass the user, but the code internally uses some logic to figure out user that's been authenticated against your created app. Here's the code I used:
Future<void> _authenticate() async {
FirebaseApp _app = await FirebaseApp.configure(
name: 'some-name',
options: FirebaseOptions(
googleAppID: 'some:id',
gcmSenderID: 'sender',
apiKey: 'api-key-goes-here',
projectID: 'some-id',
),
);
final _auth = FirebaseAuth.fromApp(_app);
final _result = await _auth.signInAnonymously();
setState(() {
app = _app;
auth = _auth;
user = _result.user;
store = Firestore(app: app); //this is the code that does the magic!
});
}
as for the serverside rule configuration - refer to #cloudwalker's answer
For me, it wound up being an issue with how I had my firestore rules set up. This is what I have now, and it works well:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow create: if isAuthenticated();
}
match /users/{userId} {
allow read, update: if isOwner(userId);
}
match /users/{userId}/{document=**} {
allow read, write: if isOwner(userId);
}
match /categories/{document=**} {
allow read: if isAuthenticated();
}
match /config/{document=**} {
allow read: if isAuthenticated();
}
}
function isAuthenticated() {
return request.auth.uid != null;
}
function isOwner(userId) {
return request.auth.uid == userId;
}
}
The solution is to initialize FirebaseStorage after getting authenticated user
Future<Null> main() async {
WidgetsFlutterBinding.ensureInitialized();
AppInfo.app = await Firebase.initializeApp(options: AppInfo.firebaseOptions);
}
It's easier if you have a listener
FirebaseAuth.instance.authStateChanges().listen((authUser) async {
if (authUser != null && AppInfo.authUser == null) {
AppInfo.authUser = authUser;
print("Got Firebase user: " + authUser.uid);
setState(() {
if (AppInfo.instance == null) {
AppInfo.instance =
FirebaseStorage(app: AppInfo.app, storageBucket: BUCKETGS);
AppInfo.instanceTemp =
FirebaseStorage(app: AppInfo.app, storageBucket: BUCKETTEMP);
}
AppInfo.userAuthId = authUser.uid;
streamsStartVerify();
});
} else if (authUser == null && AppInfo.authUser != null) {
print("sign out!!!");
AppInfo.authUser = null;
AppInfo.userAuthId = "";
}
});
}