Update Firebase Realtime Data Base Specific Value in an Object - firebase

I am learning Flutter with Firebase. I have some dummy data in Firebase Realtime Data base as:
{
"postsnode": {
"posts": [
{
"postId": "u1",
"postName": "p1"
}
]
},
"usersnode": {
"users": [
{
"userId": "u1",
"userName": "bla bla 1"
},
{
"userId": "u2",
"userName": "bla bla 2"
}
]
}
}
The screen shot of the console database structure is:
I have successfully performed a query on my usersnode object to get the specific users:
void queryDB(BuildContext context) async {
AppUtil.showLoader(context: context);
FirebaseDatabase.instance.ref().child('usersnode/users').orderByChild('userId').equalTo("u1").get().then((snapshot) {
if (snapshot.exists) {
print("user:::" + snapshot.value.toString());
final jsonResponse = json.encode(snapshot.value);
List<dynamic> list = json.decode(jsonResponse);
List<Users> users = [];
list.forEach((element) {
Users usersModel = Users.fromJson(element);
users.add(usersModel);
});
users.forEach((element) {
debugPrint("UseX:::" + element.userName);
});
} else {
print('No data available.1');
}
AppUtil.dismissLoader(context: context);
// }).onError((error, stackTrace) {
// print('No data available.2');
// AppUtil.dismissLoader(context: context);
});
}
I just want to update my specific object suppose the users object with the userId=u1 only!
Can somebody help me getting this, update using the query! or I'm doing the wrong way!
Actually I just simple want to update the object based on some condition:
Update Users -> where userId=u1
Thanks in Advance!

Welcome to Flutter Firebase!
First, you should probably change your data structure. There's no need to have a random incremental ID for users. You can use their unique ID as the key for the object:
{
"posts": {}
...
"users": {
"u1": {
"userName": "bla bla 1"
},
"u2": {
"userName": "bla bla 2"
}
}
}
Then, you can do this:
final String userId = 'u1';
final userDoc = FirebaseDatabase.instance.reference().child('users/$userId');
If you're planning on storing a lot of data in posts or users, I highly recommend that you check Cloud Firestore. It offers better structure and more advanced data types.

You're starting with a very flimsy foundation to your database if you go down the route you're taking. A proper schema would look like this:
{
"posts": {
"$postID": {
"postName": "Some Name"
// ...
}
}
"users": {
"$userID": {
"userName": "Some Username"
}
}
}
You would be wise to avoid arrays entirely when using firebase database. This isn't a firebase standard, it's a NoSQL practice.
In short, NoSQL databases use key value pairs to structure data. Since you won't have any two users with the same ids, nor two posts with the same ids, using key-value pairs in your database let you easily accomplish what you're trying to do by design.
I just want to update my specific object suppose the users object with
the userId=u1 only! Can somebody help me getting this, update using
the query! or I'm doing the wrong way!
The way to do this in the user nodes is as simple as this:
String userID = "$userID"; // where $userID is your targeted user
String newUserName = "$newUsername"; // this is the new data you're trying to change
DatabaseReference userRef = FirebaseDatabase.instance.ref().child('users').child(userID);
userRef.update({
"userName": newUserName,
"timestamp": ServerValue.timestamp,
});
I have successfully performed a query on my usersnode object to get the specific users:
Obviously your old implementation won't work when you update your schema to be key-value paired. But that's a good thing because now you can do proper queries which you can then convert into arrays in the app. You would effectively parse through the DatabaseSnapshot value, which is a Map where the keys are the userIDS and the values are corresponding data.
The way you would load the users into your app with is as follows:
DatabaseReference usersRef = FirebaseDatabase.instance.ref().child('users');
usersRef.orderByChild("userName").once(DatabaseEventType.value).then((DatabaseEvent databaseEvent) {
// (You should update to the latest package which has breaking changes like this you need to adjust to)
// First get the snapshot from the new DatabaseEvent object
DataSnapshot snapshot = databaseEvent.snapshot;
// Convert the value into a map you can parse through
Object? snapshotValue = snapshot.value;
Map<dynamic, dynamic> valueMap = snapshotValue is Map<dynamic, dynamic> ? snapshotValue : {};
// Print the results for debugging
if(valueMap.isEmpty) {
debugPrint("No users found");
} else {
debugPrint("${valueMap.length} user${valueMap.length == 1 ? '' : 's'} found");
}
// Loop through the keys of this map (which are the userIDS)
List<Users> users = [];
for(String userID in valueMap.keys) {
Users userModel = Users.fromJson(valueMap[userID]);
users.add(usersModel);
debugPrint("UseX:::" + userModel.userName);
}
AppUtil.dismissLoader(context: context);
});
Lastly, if you allow users to create posts, you might want to consider adding the following node to your schema in addition to just "posts":
"user_posts" {
"$userID": {
"$postID": POST_OBJECT,
// ...
}
}
Then when you want to load the posts created by the user, rather than only being able to query the "posts" node for posts created by the user (which you'll have a limited querying ability), you query the posts under this specific user's posts (by timestamp for example).
If you do it this way, you'd store a copy of the same object under the user's posts node and posts' node.
You seem new to firebase and have a limited understanding of how the database works. It can get very messy very fast and the most important thing to do is understand how you're going to use the data, how it's going to be queried, and most importantly, how you want to be able to sort it (by alphabetical order, by ranking, by number of likes, by date created, etc.).
Understanding these concepts now is very important because it's directly tied to your database security rules as well (which you wouldn't be able to protect with the way you were doing it before).
My advice would be to look up a quick start guide on NoSQL databases (maybe just the firebase documentation starting guide), look at the firebase database security rules guide, understand what fanning out data is, and then start looking into cloud functions so you can do useful things when data is created, updated, or deleted.
It's very easy to neglect this stuff early on while only focusing only on the app, but this is equally important despite not being something your end user would spend a second thinking about. Without a solid database, you'll run into endless issues in the future.
Good luck :)

Related

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

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

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

Firestore transactions with security rules making reads

I want to create two documents
Account/{uid} {
consumerId: ... //client generated id
}
Consumer/{consumerId} {
...
}
and I have a security rule for the consumer collection
match /Consumer/{consumerId} {
allow create: if (consumerId == get(/databases/$(database)/documents/Account/$(request.auth.uid)).data['consumerId'];
}
I need to ensure that an account can only add a consumer document with a consumerId corresponding to the one in their Account document. Both documents should be created together. I've been trying to do this with transactions but I keep getting the error "Transaction failed all retries.". Whats going wrong and how do I fix it?
The data variable is an object and not an array, so you should use data.consumerId instead of data['consumerId']:
match /Consumer/{consumerId} {
allow create: if consumerId == get(/databases/$(database)/documents/Account/$(request.auth.uid)).data.consumerId;
}
I ended up accomplishing this with a batch write and security rules.
match /consumer/{cid} {
function isNewResource() { return resource == null; }
allow create: if isRegistered();
allow read, update: if isNewResource();
}
And then client side with something along the lines of
createThing() {
const db = firebase.firestore();
const { uid, displayName } = this.auth.currentUser;
const batch = this.db.batch();
// Essentially generating a uuid
const newConsumerRef = db.collection("consumer").doc();
// Update the user doc
batch.update(
db.collection('/users').doc(uid),
{ consumerID: newConsuemrRef.id }
);
// Update the admins field in the new doc
batch.set(newConsumerRef, {
admins: {
[uid]: displayName,
},
});
return batch.commit();
}
My problem was the same, but the write to the field in the collections actually needed to be to an object key, so it looked a little funkier
batch.update(
db.collection('/users').doc(uid),
{ [`adminOf.${newRef.id}`]: 'some special name' }
);

Query List of Maps in DynamoDB

I am trying to filter list of maps from a dynamodb table which is of the following format.
{
id: "Number",
users: {
{ userEmail: abc#gmail.com, age:"23" },
{ userEmail: de#gmail.com, age:"41" }
}
}
I need to get the data of the user with userEmail as "abc#gmail.com". Currently I am doing it using the following dynamodb query. Is there any another efficient way to solve this issue ?
var params = {
TableName: 'users',
Key:{
'id': id
}
};
var docClient = new AWS.DynamoDB.DocumentClient();
docClient.get(params, function (err, data) {
if (!err) {
const users = data.Item.users;
const user = users.filter(function (user) {
return user.email == userEmail;
});
// filtered has the required user in it
});
The only way you can get a single item in dynamo by id if you have a table with a partition key. So you need to have a table that looks like:
Email (string) - partition key
Id (some-type) - user id
...other relevant user data
Unfortunately, since a nested field cannot be a partition key you will have to maintain a separate table here and won't be able to use an index in DynamoDB (neither LSI, nor GSI).
It's a common pattern in NoSQL to duplicate data, so there is nothing unusual in it. If you were using Java, you could use transactions library, to ensure that both tables are in sync.
If you are not going to use Java you could read DynamoDB stream of the original database (where emails are nested fields) and update the new table (where emails are partition keys) when an original table is updated.

Structuring data in Firebase mixing fields as ID

I'm new using Firebase and NoSQL databases. I wonder if it's possible to do this:
Having an user database I want to fetch data by email or UID. Is it optimal to mix both fields as ID and later filter with queryStartingAtValue or queryEndingAtValuemethods?
{
"users": {
"user1#gmail.comDjDJSADfgg": {
dataUser1: ----- },
"user2#gmail.comfmadaDkK": {
dataUser2: ----- },
"user3#gmail.com3Dkdjakdja4": {
dataUser3: ----- },
"user4#gmail.comKdsadASD": {
dataUser4: ------ },
}
}
Although you can use queryStartingAtValue and queryEndingAtValue, this solution is not so clear, imagine that someone else reads your code (or even you after 6 months), would he/she knows what you meant by queryStartingAtValue? Probably not.
I think is clearer to use orderByChild and equalTo
e.g.
var ref = new Firebase("yourdb");
ref.orderByChild("email").equalTo("blahblah#gmail.com")
.on( anEvent, function(snapshot) {
console.log(snapshot.key());
});

Resources