Firebase Multipath updates overwrites instead of update children - android

I am using Firebase real-time database in my Android app. For data normalization purposes, I'm saving Player's data to multiple paths.
When I save it to each path separately all works fine. But I tried to save the data using Multiple-Path update and then instead of updating existing children, this overwrites existing data (like in setValue). I read about this phenomenon somewhere but I can't get it to work as it should.
Here is the relevant snippet of my code:
public void createPlayerInFirebaseDatabse(String playerId, FirebasePlayerEntity firebasePlayerEntity, final ICreateUser listener) {
Log.e(TAG, "DBHelper.createPlayerInFirebaseDatabase");
Map<String, Object> player = new HashMap<>();
player.put(playerId, firebasePlayerEntity);
Map<String, Object> isPlayerIn = new HashMap<>();
isPlayerIn.put(playerId, true);
Map<String, Object> playerUpdates = new HashMap<>();
playerUpdates.put("players/", player);
playerUpdates.put("leagues/" + firebasePlayerEntity.getLeagueCode() + "/playersIds/", isPlayerIn);
playerUpdates.put("teams/" + firebasePlayerEntity.getTeam() + "/playersIds", isPlayerIn);
databaseReference.updateChildren(playerUpdates)
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
Log.e(TAG, "DBHelper.createPlayerInFirebaseDatabase: onSuccess");
listener.onSuccess();
} else {
listener.onError(ErrorConsts.ERROR_CODE_DB_WRITING_FAILED);
}
}
});
It only updates the data (not overwriting) when I save data separately to each path, like this:
databaseReference.child(REF_PLAYERS).updateChildren(player);
databaseReference.child(REF_LEAGUES + "/" + firebasePlayerEntity.getLeagueCode() + "/" + REF_PLAYERS_IDS).updateChildren(isPlayerIn);
databaseReference.child(REF_TEAMS + "/" + firebasePlayerEntity.getTeam() + "/" + REF_PLAYERS_IDS).updateChildren(isPlayerIn)
I found this topic on javascript Firebase multi-location update overwriting instead of updating value
but I was wondering if anyone experienced it on Android and managed to solve this issue.
Any help would be appreciated. thanks!

A multi-location update loops over each key in the Map that you pass in, and the performs a setValue on that path. It does not do a "deep merge", so anything at any specific key in your map will be replaced. For this reason, you need to make sure that the paths in your map are to the exact data that you want to replace.
Right now your map keys point one level to high. For example your maps says to update players with $playeruid/<playerEntity>. This means that you're replacing everything under players with the data for the new player, which is not what you want. What you instead should do is tell Firebase to update players/$playeruid to <playerEntity>, so making $playeruid part of the path/key.
In code:
Map<String, Object> playerUpdates = new HashMap<>();
playerUpdates.put("players/" + playerId, firebasePlayerEntity);
playerUpdates.put("leagues/" + firebasePlayerEntity.getLeagueCode() + "/playersIds/" + playerId, true);
playerUpdates.put("teams/" + firebasePlayerEntity.getTeam() + "/playersIds/" + playerId, true);
databaseReference.updateChildren(playerUpdates)

Related

How to retrieve data on firebase firestore Android?

I have been searching for quite a while now and I can't seem to find anything to help me...
I want to fetch data from a firebase firestore collection that I have in reference (timeEntryTable). In the debugger, I can see that my data is accessed correctly, but when I get out of the method, it seems like everything is gone...
private void getTimes(){
float totalTime = 0;
timeEntryTable
.whereEqualTo("person", "Alexandre")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
TimeEntry timeEntry = new TimeEntry(document.getData());
times.add(timeEntry.getTime());
Log.d(TAG, document.getId() + " => " + document.getData());
}
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
System.out.println("times = " + times);
}
In this snippet, times is a private ArrayList member and TimeEntry is a POJO that has the structure of the documents in the collection.
I can really see in the debugger that times is filled with the right data, but when I call System.out.println("times = " + times);, the value is []... Is there something I am not doing correctly?
Thank you :)
To add to the previous answer that "the first time you can use the results is inside the callback itself", move the "System.out.println("times = " + times);" line right outside underneath your for loop.
Firestore's get() operation, and all of its APIs, are asynchronous and return immediately, before the data is available. Your code continues to execute while the query completes. Some time later, the callback you attached with addOnCompleteListener will be invoked with the results of the query.
The first time you can use the results is inside the callback itself. You can't be certain that any other access to your times array will contain anything. This means that you will have to build your program around these asynchronous APIs instead of depending on line-by-line execution.

How to add multiple values to same field in Firestore

I am trying to add multiple image Uri's to the same field in Firestore.
.
I have been trying several methods, instead of adding image Uris to the same field it creates multiple documents in fire store. I need help.
for (int i = 0; i < imageUris.size(); i++) {
Toast.makeText(getApplicationContext(), "ROUND:" + i, Toast.LENGTH_SHORT).show();
imgUri = imageUris.get(i);
imageFilesPath = storageReference.child("Ads_images" + "/" + "Ad_" + UUID.randomUUID().toString() + ".jpg");
imageFilesPath.putFile(imgUri).addOnCompleteListener(new OnCompleteListener<UploadTask.TaskSnapshot>() {
#Override
public void onComplete(#NonNull Task<UploadTask.TaskSnapshot> task) {
if (task.isSuccessful() && task != null) {
LIST.add(task.getResult().getDownloadUrl().toString());
Log.e(TAG, "List inside"+LIST.size());
Map<String, Object> adMap = new HashMap<>();
adMap.put("user_id", user_id);
adMap.put("description", "Small description is shown here");
adMap.put("imageUris", Arrays.asList(LIST.get(0)));
firebaseFirestore.collection("Ads").add(adMap);
}else {
}
}
});
Every time you call add() on a collection, Firestore creates a new document. Since you now call add() after uploading each file, you get a separate document for each file. If you want to add your URL to an existing document, you must get a DocumentReference to that document and update it.
firebaseFirestore.collection("Ads").doc("v1ys3tyQ7vhHLD0ietAw").update(adMap);
To add a URL to the imageUris array, you will need to first load the current contents of that array (to determine what index to add the item to). This read-to-update sequence is somewhat inefficient at scale. If that matters to your app, you might want to consider storing the URLs in a map inside the document instead, or storing them in a subcollection under the document.
Map<String,Object> imageUrl = new HashMap<>();
imageUrl.put("ImageUrl", imageList);
firebaseFirestore.collection("ImageDetails").document(userId)
.set(imageUrl,SetOptions.merge());
this worked fine for me too, but i passed the list separately once all the list items were added,here imageUrl is my map object and imageList is an arraylist for storing generated image uri download links.
Also by any chance could I get the look at the code at how you fetched back all those image uri

Build the data structure with relationship via Firebase

I am new in noSQL and Firebase. But I want to build the structure of my database via Firebase.
I have users and a list of users lots. Structure with relationship.
So I did what's in the example:
String key = mDatabase.child("lots").push().getKey();
//create new lot
Lot lot = new Lot(fbUser.getUid(), fbUser.getEmail(), mMessage.getText().toString());
//turn to map
Map<String, Object> lotValues = lot.toMap();
Map<String, Object> childUpdates = new HashMap<>();
childUpdates.put("/lots/" + key, lotValues);
childUpdates.put("/user-lots/" + fbUser.getUid() + "/" + key, lotValues);
mDatabase.updateChildren(childUpdates);
But in the result I had this data to duplicate:
May be it's better to get this structure. I tried to find an example how to build one, because I do not want to to reinvent a wheel, but my efforts are futile so far.
Any suggestions? Thanks a lot.
What you're looking for is Pointers, which Firebase, believe it or not, DOES NOT have.
For example, if you want to have 3 lists of data:
My Posts
Recent Posts
Then you'll have to do it like this:
databaseRoot:{
Posts:{
{UNQ_KEY}:
{
title: "x",
description: "y",
authorUserID: {USERID1}
}
}
Users:{
{USERID1}
Posts:{
{UNQ_KEY_2}: {
title: "x",
description: "y"
}
}
}
}
When UNQ_KEY is created, you also create UNQ_KEY_2 under the user's userID.
To display "My Posts", you get the list under {USERID1}. To get "Recent Posts", you have to go to the Posts node.

Need Solution when getting DatabaseException: Path is an ancestor in Firebase

Following up on a similliar question like this one, I just want to get an answer if this is a limitation in FirebaseDatabase.updateChildrenor if I do something wrong.
I understand I cannot specify an update for /USER and then specify a different update for /USER/ + getUid() + "/" + "fancy" + "/" + pushKey
If I still need this two updates to be atomic what should I do?
childUpdates.put(USER + "/" + getUid(), map1);
childUpdates.put(USER + "/" + getUid() + "/" + "fancy" + "/" + pushKey, map2);
// Do a deep-path update
ref.updateChildren(childUpdates, new DatabaseReference.CompletionLi....
UPDATE adding clarification:
The values are Strings
this is map1
//
// FAN_OUT - User.LAST_VISITED_ADDRESS
//
Map<String, Object> map1 = new HashMap<String, Object>();
map1.put("last_visited_address", "pushKey");
this is map2
//
// FAN_OUT - User.FANCY
//
Map<String, Object> map2 = new HashMap();
map2.put("owner", "a path");
When you run a multi-location update statement, the Firebase Database loops over the map you provide. For each entry it updates the location/key with the value you provided. The order in which the entries are executed is unspecified.
This means that if you have two conflicting keys/paths, it is undetermined what the result will be. E.g.
/path/to/object { name: "Erik Hellberg" }
/path/to/object/id 6820170
If the server would execute the operations in the order I have them above, the result would be:
path
to
object
id : 6820170
name: "Erik Hellberg"
But if it happened to execute them in the reverse order, the result would be:
path
to
object
id : 6820170
Since an indeterministic operation is pretty useless, the Database rejects the update.
Deterministic ways to get the result are:
/path/to/object { id: 6820170, name: "Erik Hellberg" }
And:
/path/to/object/name "Erik Hellberg"
/path/to/object/id 6820170
Since there is no overlap in update paths here, there is no conflict.

Firebase , second push() in same method does not work

In my app , there is an activity which upon clicking the save button, 2 sets of data in 2 different places should be made by push(), Since in both places an unique id is needed.
I have followed the sample code in the Firebase guide and added the second push
String userId = auth.getCurrentUser().getUid().toString();
DatabaseReference reference = FirebaseDatabase.getInstance().getReference();
DatabaseReference firstDatabaseRef = reference.child("first");
DatabaseReference secondDatabaseRef = reference.child("second").child(userId);
String key = firstDatabaseRef.child(userId).push().getKey();
First first = new First(firstAmount,key,firstName);
Map<String, Object> firstValues = first.toMap();
String keySecond = secondDatabaseRef.child(key).push().getKey();
Second second = new Second(secondName,secondAmount,keySecond,key);
Map<String, Object> secondValue = second.toMap();
Map<String, Object> childUpdates = new HashMap<>();
childUpdates.put("/first/" + userId + "/" + key, firstValues);
childUpdates.put("/second/" + userId + "/" + key + "/" + keySecond, secondValue);
reference.updateChildren(childUpdates);
The result that i got for first was exactly as i expected but for second , instead of creating second/<userId>/<key>/<keySecond>/children, i get this :
"second" : {
//userId
"5TQLPlGf4mbcBRKesQwR30fH1L22" : {
//key
"-KL1030IywlNpkTGC7mU" : {
"secondAmount" : "147",
"Key" : "-KL1030IywlNpkTGC7mU",
"secondName" : "secondName",
"keySecond" : "-KL1030PZlHqD_asSR_8",
}
}
}
Instead of having the final children in another unique id, which by the way is recorded in the keySecond, they are all added directly to the key.
This cannot be accepted since every key must have many keySecond.
I hope that i explained my problem correctly.
Please tell me what am i doing wrong.
How should i modify my code or should i reconsider my data structure completely ?
This is a puzzle. I copy/pasted the code you posted and created stubs for First and Second. Running with Firebase 9.0.2 produced the result shown below, which I believe is what you are expecting. Are you running with a different Firebase version? Is it possible the JSON you posted was produced by a different version of the code you posted?
{
"first" : {
// userId
"ypx8RB3eglTBRPeUT7laQVQ1PZQ2" : {
// key
"-KL3rXeYrscFQNrVQnHb" : {
"firstAmount" : "FirstAmount",
"firstKey" : "-KL3rXeYrscFQNrVQnHb",
"firstName" : "FirstName"
}
}
},
"second" : {
// userId
"ypx8RB3eglTBRPeUT7laQVQ1PZQ2" : {
// key
"-KL3rXeYrscFQNrVQnHb" : {
// keySecond
"-KL3rXe_JyY9Vz2U-NES" : {
"Key" : "-KL3rXeYrscFQNrVQnHb",
"keySecond" : "-KL3rXe_JyY9Vz2U-NES",
"secondAmount" : "SecondAmount",
"secondName" : "SecondName"
}
}
}
}
}
When processing the updateChildren(), the Firebase Database loops over the children that you pass and for each key it essentially does a setValue() with the value you passed.
That means that if you have children with overlapping keys, the value of one of those keys will be written last. The order of these is undefined.
In your case it's fairly easy to merge the updates together:
String key = firstDatabaseRef.child(userId).push().getKey();
First first = new First(firstAmount,key,firstName);
Map<String, Object> firstValues = first.toMap();
String keySecond = secondDatabaseRef.child(key).push().getKey();
Second second = new Second(secondName,secondAmount,keySecond,key);
Map<String, Object> secondValue = trans.toMap();
firstValues.put(keySecond, secondValue); // This is the main change
Map<String, Object> childUpdates = new HashMap<>();
childUpdates.put("/first/" + userId + "/" + key, firstValues);
reference.updateChildren(childUpdates);

Categories

Resources