Flutter-Firebase RTDB : Is Push ID mandatory for reading data from Firebase Realtime database? - firebase

My current experiment:
I want user to sign in and enter details
When next time he signs in, i want to retrieve his saved information
My intended database structure:
-"users
------"uid"
-----------firstName
-----------lastName`
I use the below code in flutter to create records:
await FirebaseAuth.instance.signInWithEmailAndPassword( email: email, password:
password).then((value)
{
if(value.user.uid != null )
{
final dbRef = FirebaseDatabase.instance.reference().child('users');
dbRef.push().child(user.uid.toString()).set(User().toJson());
}
}
the data gets created with a push key/ID inbetween:
-users
------MFvvXpeRmoQvXkd5VS8 `<---Push ID generated by Firebase`
--------------k8IL4xLQKRf82dxlXNLSHEt2
-----------------------firstName: "sadsadda"
------------------------lastName: "asdsadsad"`
Based on documentations, When i try to retrieve the data using the following code:
final dbRef = FirebaseDatabase.instance.reference().child('users').child(user.uid.toString());
dbRef.equalTo(user.uid.toString()).once().then((snapshot)
{
/*snapshot has value null
}
);
//I even added listener<br>
dbRef.onChildAdded.listen((event) {
readUserInfo(event.snapshot); // even here snapshot value is null.
});
Just for testing purpose, i tried to pass the push key in-between by hardcoding,
final dbRef = FirebaseDatabase.instance.reference().child('users').child('-
MFvvXpeRmoQvXkd5VS8').child(user.uid.toString());`
then "onChildAdded" listener was able to pickup the child entries.
Is this the expected behaviour ? or is there a way to avoid this randomly generated push id?
Thanks for your time in advance

To write the user data under their UID, don't call push(), but simply do:
dbRef.child(user.uid.toString()).set(User().toJson());
To then read the data for the user back, use:
final dbRef = FirebaseDatabase.instance.reference().child('users').child(user.uid.toString());
dbRef.onValue.listen((event) {
readUserInfo(event.snapshot);
});
The .childAdded is needed when you want to read a list of child nodes, for example to read all users:
final dbRef = FirebaseDatabase.instance.reference().child('users');
dbRef.onChildAdded.listen((event) {
readUserInfo(event.snapshot);
});

Related

Flutter : how to get newly added record from firebase real time database?

I'm using below code to retrieve all the data from my firebase chat room
void readDataFromFireBase() {
var databaseReference =
FirebaseDatabase.instance.reference().child("messages");
databaseReference
.child(chatroom.chatId)
.once()
.then((DataSnapshot snapshot) {
Map<dynamic, dynamic> values = snapshot.value;
values.forEach((key, value) {
setState(() {
chatMessageList.add(ChatMessage(
value["message"], value["fromUser"], value["timestamp"]));
chatMessageList.sort((a, b) {
var formatter = new DateFormat('MM/dd/yyyy, kk:mm');
var dateOne = formatter.parse(a.timestamp);
var selectedDate = formatter.parse(b.timestamp);
return dateOne.compareTo(selectedDate);
});
});
});
}
now how can i get notify my chat room when the new message has arrived
currently i'm using below code to listen child added event
listenDataFromFireBase() {
var db = FirebaseDatabase.instance.reference().child("messages");
db.child(chatroom.chatId).onChildAdded.listen((data) {
print("GET_NEW_DATA");
print(data.snapshot.value["message"] ?? '');
print(data.snapshot.value["fromUser"] ?? false);
print(data.snapshot.value["timestamp"] ?? '');
});
}
but there is one issue i'm facing this listenDataFromFireBase() load all the the data from particular room
My requirement is whenever new message added in chatroom i want to animate my message layout
How can i get notify my screen whenever new message will add in my chat room.
If need more information please do let me know. Thanks in advance. Your efforts will be appreciated.
As you've discovered onChildAdded fires immediately for any existing data, and after that also when any data is added. If you want to distinguish between these two cases, you'll need to combine an onChild listener and an onValue listener.
In the onValue listener, all you do is flag whether that event has happened:
databaseReference.onValue.listen((event) {
setState(() {
isInitialDataLoaded = true
});
});
Now do all your data processing in the onChildAdded listener, getting the message from the snapshot and adding it to the list. Then use the isInitialDataLoaded to detect whether this is initial data, or an update:
var db = FirebaseDatabase.instance.reference().child("messages");
db.child(chatroom.chatId).onChildAdded.listen((data) {
// TODO: get message from snapshot and add to list
if (isInitialDataLoaded) {
// TODO: alert the view about the new data
}
});
So you'll have two listeners on the same node. The Firebase SDK actually detects this situation and only reads the data from the server once, so there is no extra data being transferred in this case.
You can use onValue:
/// Fires when the data at this location is updated. `previousChildKey` is null.
Stream<Event> get onValue => _observe(_EventType.value);
But if you use onValue or onChildAdded, it will retrieve all the data under this chatroom.chatId, then when you data is added the onValue event will be fired again and will give you the new data.

Flutter - Getting data from firestore in a loop

So basically I have a collection a User and within each user there is a subcollection for the pending friend request that the user have, something like that:
/users/[auto-id]/friend_requests/[auto-id]/{user: ref to another user}
But one user can obviously have multiple requests at the same time and I have an hard time to get the data correctly.
What I'm correctly trying to do is to get a list of user that are in the subcollection "friend_requests":
_loadFriendRequests() async {
try {
this._users.addAll(await _dataService.fetchFriend());
} catch (e, stackTrace) {
printException(e, stackTrace, "Error loading friends");
}
}
And in dataService:
Future<List<User>> fetchFriend() async {
final querySnapshot =
await _currentUserDoc.reference.collection("friend_requests").getDocuments();
return await Future.wait(querySnapshot.documents.map((doc) async {
final user = await doc["user"].get();
User(
id: user["id"],
name: user["name"],
);
}).toList(growable: false));
}
This is just the last thing that I tried but I tried in so many ways, with Future.wait() too, with/without async/await, etc
I just can't seem to make it work...
You're missing a return statement after final user = await doc["user"].get();
This could become a lot easier if you use Streams or fancy rxdart

Flutter/Dart app breaking due to change on Firebase Date object

My code:
void createCloudStoreRecord(FirebaseUser user) {
// Create/Update user record with uid
final DocumentReference docRefUsers = Firestore.instance.collection("users").document(user.uid);
docRefUsers.get().then((datasnapshot) {
if (datasnapshot.exists) {
// Exists, so do nothing
} else {
// Does not exist, let's create
Map<String, String> data = <String, String> {
"email": user.email,
"photoURL": user.photoUrl,
};
// Save data
docRefUsers.setData(data).whenComplete(() {
}).catchError((e) => print(e));
}
});
Error:
5.0.0 - [Firebase/Firestore][I-FST000001]
The behavior for system Date objects stored in Firestore is going to change AND YOUR APP MAY BREAK.
To hide this warning and ensure your app does not break, you need to add the following code to your app before calling any other Cloud Firestore methods:
let db = Firestore.firestore()
let settings = db.settings
settings.areTimestampsInSnapshotsEnabled = true
db.settings = settings
With this change, timestamps stored in Cloud Firestore will be read back as Firebase Timestamp objects instead of as system Date objects. So you will also need to update code expecting a Date to instead expect a Timestamp. or example:
// old:
let date: Date = documentSnapshot.get("created_at") as! Date
// new:
let timestamp: Timestamp = documentSnapshot.get("created_at") as! Timestamp
let date: Date = timestamp.dateValue()
Please audit all existing usages of Date when you enable the new behavior. In a future release, the behavior will be changed to the new behavior, so if you do not follow these steps, YOUR APP MAY BREAK.
Question is: I am just creating a new record; I have not use any Date object at all. Can I safely ignore this warning?
It is safe to ignore, though with v0.8.2 or the Firebase plugin you can avoid the warning by using the settings:
await firestore.settings(timestampsInSnapshotsEnabled: true);

Firebase get value from database

The question is: How to take the uid row when you only know the user his username? for example, you only know 'senneken' and you want to know the uid of 'senneken'
Extra information:
My user database looks like this
I want to add friends to users. I can check if the user exists for them by doing
searchButton.addEventListener('click', function (event) {
event.preventDefault();
username = searchUsername.value;
var ref = firebase.database().ref('users').orderByChild("username").equalTo(username).once("value", snapshot => {
const userData = snapshot.val();
if (userData) {
console.log("Username " + username + " was found");
} else {
console.log("No user found");
}
But now I want to add the users UID in my database
And I can add the friends username by doing
addFriendButton.addEventListener('click', function (event) {
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
var ref = firebase.database().ref("users").child(user.uid).child("friends").push({
username: username
})
}
});
});
Because I use push there is always a random ID generated under friends but I would like to take the UID from the user that I want to add and put that under my friends (instead of the random UID)
In this case the collection of friends seems like a set: each specific UID can either be in there, or it cannot be in there. It cannot be in there more than once, and order seems to not matter. The solution is to not use a push ID, but model it as a set like this:
friends
uid1
uid2: true
uid3: true
This way you can simply set a user as a friend with:
firebase.database().ref("friends").child(user.uid).child(username).set(true)
You might notice that I also turned the collections of friends into a top-level collection. Nesting information about friends under other profile information about a user is an anti-pattern, which makes it hard to secure data, leads to downloading more data than is needed, and in general is not recommended by Firebase experts.

Is Firebase Database searchable using objects instead of references, with AngularFire $firebaseArray $keyAt(recordOrIndex)?

After a user logs in using $firebaseAuth, Google sends the user's displayName, email, and photoURL. I then want to look up the user's account in my Firebase database. I can't use $getRecord(key) because Google doesn't tell me the user's key. It appears that I should use $keyAt(recordOrIndex), and then use $getRecord(key). $keyAt(recordOrIndex) works fine with an index. $keyAt(recordOrIndex) works fine with a record that I retrieved with $getRecord(key). I can't get $keyAt(recordOrIndex) to work with an object that I made from the user data that Google returned using $firebaseAuth.
I tried both the complete object (displayName, email, photoURL) and an object consisting of only the email address. The latter is what I would prefer to use. Neither worked.
app.controller('LoginModalInstanceCtrl', ['$scope', '$location', '$uibModalInstance', '$firebaseArray', '$firebaseObject', '$firebaseAuth', function($scope, $location, $uibModalInstance, $firebaseArray, $firebaseObject, $firebaseAuth) {
// Create Firebase3 reference
var ref = firebase.database().ref();
// Set up Firebase Auth
$scope.authObj = $firebaseAuth();
var authData = $scope.authObj.$getAuth();
$scope.authData = authData;
// Google OAuth login handler
$scope.loginGoogle = function() {
$scope.authData = null;
$scope.error = null;
$scope.authObj.$signInWithPopup("google")
.then(function(authData) {
$scope.authData = authData;
console.log(authData);
console.log("Your displayName is:", authData.user.displayName);
console.log("Your email is:", authData.user.email);
console.log("Your photoURL is:", authData.user.photoURL);
var record = {
displayName: authData.user.displayName,
email: authData.user.email,
photoURL: authData.user.photoURL
};
var emailObject = {
email: authData.user.email
};
// look up account
var users = $firebaseArray(ref.child('users'));
users.$loaded()
.then(function() {
console.log("Array loaded!");
var key1 = users.$keyAt(1);
console.log(key1); // -Khi6OxAo339ye6xoG3i
var record = users.$getRecord(key1);
console.log(record); // Object with displayName, email, and photoURL
var key1 = users.$keyAt(record);
console.log(key1); // -Khi6OxAo339ye6xoG3i
var objectKey = users.$keyAt(object);
console.log(objectKey); // null
var emailKey = users.$keyAt(emailObject);
console.log(emailKey); // null
});
$uibModalInstance.close(); // close modal window
$location.path('/languagetwo/'); // return to the homepage
}).catch(function(error) {
console.error("Authentication failed:", error);
});
};
Should I use $firebaseObject instead of $firebaseArray:
var user = $firebaseObject(ref.child('users').child( SOMETHING HERE? ));
The answer appears to be no, you can't search Firebase Database using AngularFire. (Maybe AngularFire 2 has search, I didn't look.) What I did instead was to use "plain vanilla" Firebase:
var users = firebase.database().ref('users');
users.orderByChild('email').equalTo(authData.user.email).once('value').then(function(snapshot) {
console.log(snapshot.val());
});
The first line sets up the Firebase ref and is the similar to as before, except that I'm going straight to the users array, instead of using $FirebaseArray to get to the users array.
The second line is a completely different syntax. First, you have to specify the order that you want the returned object to be in. Yes, it returns an object, not an array. I tried snapshot.val().length() and found that it's not an array. What orderByChild('email') does is to access the 'email' property of the objects in the 'users' array.
Next we do the query. equalTo(authData.user.email) returns only the objects in which the email address from $FirebaseAuth equals the email address in our 'users' array.
Next, once('value') creates a promise and waits for the async data. I tried using on() but couldn't get it to work, too many arguments or something. once() requires an argument, which can be value, child_added, child_changed, child_removed, or child_moved. The value argument is for getting data from a location without changing the child nodes.
We can then set up our then promise fulfillment. You can call the returned data anything. Here it's called snapshot.
Lastly snapshot.val() provides the data from the database, looking just like it does in the Firebase Console.

Resources