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.
Related
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)
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.
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);
I pulled my original question because I managed to figured it out through trail and error and a lot of deep searching.
So, I understand that using the latest Facebook SDK in Unity, you can pull all pending requests for a player using:
FB.API("/me/apprequests", HttpMethod.GET, RequestHandler)
Where RequestHandler is an IGraphResult, which you can then parse into a Dictionary, like so:
void RequestHandler(IGraphResult result){
if (result != null) {
Dictionary<string, object> reqResult = Json.Deserialize(result.RawResult) as Dictionary<string, object>;
}
}
The documentation explains how a singular request will be displayed in JSON format, and I've found a few examples of how to work with that information (I vaguely understand JSON's), however if pulling ALL requests for the player, how do I work with this information?
Out of the JSON, I'm just trying to pull the object ID and sender ID of each request, handle the request based on the object ID then delete the request from the graph by concatenating the two, which I think I've figured out already.
So my question is, for each request, how do I extract the object and sender ID's?
So after a LOT of trial and error and a lot of Log checks, I've figured out a really hacky way of doing it, for those that aren't sure:
public void TestRequests(){
FB.API("/me/apprequests", HttpMethod.GET, TestResponse);
}
public void TestResponse(IGraphResult result){
if (result.Error == null) {
//Grab all requests in the form of a dictionary.
Dictionary<string, object> reqResult = Json.Deserialize(result.RawResult) as Dictionary<string, object>;
//Grab 'data' and put it in a list of objects.
List<object> newObj = reqResult["data"] as List<object>;
//For every item in newObj is a separate request, so iterate on each of them separately.
for(int xx = 0; xx < newObj.Count; xx++){
Dictionary<string, object> reqConvert = newObj[0] as Dictionary<string, object>;
Dictionary<string, object> fromString = reqConvert["from"] as Dictionary<string, object>;
Dictionary<string, object> toString = reqConvert["to"] as Dictionary<string, object>;
string fromName = fromString["name"] as string;
string fromID = fromString["id"] as string;
string obID = reqConvert["id"] as string;
string message = reqConvert["message"] as string;
string toName = toString["name"] as string;
string toID = toString["id"] as string;
Debug.Log ("Object ID: " + obID);
Debug.Log ("Sender message: " + message);
Debug.Log ("Sender name: " + fromName);
Debug.Log ("Sender ID: " + fromID);
Debug.Log ("Recipient name: " + toName);
Debug.Log ("Recipient ID: " + toID);
}
}
else {
Debug.Log ("Something went wrong. " + result.Error);
}
}
Again, this is my first experience with using JSON, and I'm sure there's a much more efficient way of doing this, but basically after a lot of breaking down and converting, I've managed to extract the object ID, sender name and ID, the message attached and the recipient name and ID. The object ID comes concatenated with the recipient ID, so to operate on the object ID itself, this will need to be removed, however as is it will make it easier to pass the string on to remove the request from the Graph API.
If anyone can suggest to me a more efficient way of doing this, I'd be grateful! There's always more to learn, after all.
Okay I'll try describe this the best way I can.
I have a chat application and I am sending a request to receive a list of public channels.
The string that I receive back from the server is:
RESP_PUBLICCHANNELLIST, channelID 1, channelName 1, channelID 2, channelName 2
And when I add this list to my AlertDialog, it shows each channel and channel ID as a separate channel. I would like to just have the channel name in the AlertDialog and have the channel ID in a map with a key value to it. Is this possible?
Here is my current code I am having problems with:
public void ShowPublicChannelList(){
String PublicChannelsPost = "";
PublicChannelsPost = ExecuteCommand(_chatProtocol.ShowPublicChannelList());
System.out.println("Public Channels: " + PublicChannelsPost);
_publicChannels.add("New..");
List <String> responseList = Arrays.asList(PublicChannelsPost.split(","));
if (responseList.contains("RESP_PUBLICCHANNELLIST")){
for (int i = 1; i < responseList.size(); i++) {
_publicChannels.add(responseList.get(i));
System.out.println("Channels: " + _publicChannels);
}
SetPublicChannelList(_publicChannels);
}
Any help would be fantastic!
Thank you!
If I understand your question correctly, you're unsure about how to parse the server response into a mapping of a channel ID and name? You could simply step through your list of separated strings with steps of two and populate every pair into a single map entry. This does make the assumption that a channel id is always followed by a channel name (or vice versa: every name is preceded by an id).
if (responseList.contains("RESP_PUBLICCHANNELLIST")) {
Map<String, String> channelMap = new HashMap<String, String>();
for (int i=1; i<responseList.size(); i+=2) {
if (i+1 >= responseList.size()) break; // this only happens if not every id has name
channelMap.put(responseList.get(i), responseList.get(i+1));
}
// print values to check correct mapping
for (Map.Entry<String, String> channel : channelMap.entrySet()) System.out.println(channel.getKey() + " | " +channel.getValue());
}
As you can see, I simply break the loop if the length of the source list is 'unexpected'. you might want to consider implementing something more robust, unless you're absolutely sure the illegal case will never arise.