Saving a score to firebase with attached values - swift

What I'm wanting to accomplish is save a score to firebase that has two values attached to it. Here's the code that writes the score to firebase.
func writeToFirebase() {
DispatchQueue.global(qos: .userInteractive).async {
self.ref = Database.database().reference()
if GameManager.instance.getTopScores().contains(GameManager.instance.getGameScore()) {
self.ref?.child("user")
.child(GameManager.instance.getUsername())
.child(String(GameManager.instance.getGameScore()))
.updateChildValues( [
"badge":GameManager.instance.getBadgeLevel(),
"vehicle": GameManager.instance.getVehicleSelected()
]
)
}
}
}
The issue I'm having is when a new score is saved with its values it sometimes overwrites the other scores. This seems to be random and its not when they're the same score or anything like that. Sometimes it will only overwrite one score and sometimes multiple. I'm watching firebase and I can see it being overwritten, it turns red and then is deleted. Sometimes the new score being added will be red and get deleted. The score doesn't need to be a child, but I don't know how to attach values to it if it's not. Any help is appreciated

This issue seems to happen occasionally so I am going to post my comment as an answer.
There are situations where an observer may be added to a node and when data changes in that node, like a write or update, it will fire that observer which may then overwrite the existing data with nil.
You can see this visually in the console as when the write occurs, you can see the data change/update, then it turns red and then mysteriously vanishes.
As suggested in my comment, add a breakpoint to the function that performs the write and run the code. See if that function is called twice (or more). If that's the case, the first write is storing the data properly but upon calling it a second time, the values being written are probably nil, which then makes the node 'go away' as Firebase nodes cannot exist without a value.
Generally speaking if you see your data turn red and vanish, it's likely caused by nil values being written to the node.

Related

How can I check if the user is playing the game for the first time?

I want to show a text for the first time running like "Hi, my name is NAVI." and then when the player will start the game again the next time it will another text for example "Welcome back."
Using player prefs is probably the easiest solution, though definitely not the only one*.
Due to the lack of boolean playerpref I usually opt to use SetInt/GetInt instead, but that's personal preference. You could also do it with a string or float.
private void Start()
{
if(PlayerPrefs.GetInt("HasLaunched", 0) == 0)//Game hasn't launched before. 0 is the default value if the player pref doesn't exist yet.
{
//Code to display your first time text
}
else
{
//Code to show the returning user's text.
}
PlayerPrefs.SetInt("HasLaunched", 1); //Set to 1, so we know the user has been here before
}
Playerpref stores values between sessions, so the next session will still have "HasLaunched" set to 1. This is however stored on the local device. so users can manually reset it if they'd really want to.
If you ever want to show the "first launch" text again, simply set HasLaunched back to zero, or delete it altogether with DeleteKey("HasLaunched");
*There are plenty of alternative solutions, like storing it in a config file locally, or using a remote database (this would be required if you don't want users to be able to reset it). They would however come down to the same principle. Setting a value to true or 1 somewhere, and checking that value on launch.

Wait for backend service response before making changes to ag-grid table

I am using ag-grid/ag-grid-angular to provide an editable grid of data backed by a database. When a user edits a cell I want to be able to post the update to the backend service and if the request is successful update the grid and if not undo the user's changes and show an error.
I have approached this problem from a couple different angles but have yet to find the solution that meets all my requirements and am also curious about what the best practice would be to implement this kind of functionality.
My first thought was to leverage the cellValueChanged event. With this approach I can see the old and new values and then make a call to my service to update the database. If the request is successful then everything is great and works as expected. However, if the request fails for some reason then I need to be able to undo the user's changes. Since I have access to the old value I can easily do something like event.node.setDataValue(event.column, event.oldValue) to revert the user's changes. However, since I am updating the grid again this actually triggers the cellValueChanged event a second time. I have no way of knowing that this is the result of undoing the user's changes so I unnecessarily make a call to my service again to update the data even though the original request was never successful in updating the data.
I have also tried using a custom cell editor to get in between when the user is finished editing a cell and when the grid is actually updated. However, it appears that there is no way to integrate an async method in any of these classes to be able to wait for a response from the server to decide whether or not to actually apply the user's changes. E.g.
isCancelBeforeStart(): boolean {
this.service.updateData(event.data).subscribe(() => {
return false;
}, error => {
return true;
});
}
does not work because this method is synchronous and I need to be able to wait for a response from my service before deciding whether to cancel the edit or not.
Is there something I am missing or not taking in to account? Or another way to approach this problem to get my intended functionality? I realize this could be handled much easier with dedicated edit/save buttons but I am ideally looking for an interactive grid that is saving the changes to the backend as the user is making changes and providing feedback in cases where something went wrong.
Any help/feedback is greatly appreciated!
I understand what you are trying to do, and I think that the best approach is going to be to use a "valueSetter" function on each of your editable columns.
With a valueSetter, the grid's value will not be directly updated - you will have to update your bound data to have it reflected in the grid.
When the valueSetter is called by the grid at the end of the edit, you'll probably want to record the original value somehow, update your bound data (so that the grid will reflect the change), and then kick off the back-end save, and return immediately from the valueSetter function.
(It's important to return immediately from the valueSetter function to keep the grid responsive. Since the valueSetter call from the grid is synchronous, if you try to wait for the server response, you're going to lock up the grid while you're waiting.)
Then, if the back-end update succeeds, there's nothing to do, and if it fails, you can update your bound data to reflect the original value.
With this method, you won't have the problem of listening for the cellValueChanged event.
The one issue that you might have to deal with is what to do if the user changes the cell value, and then changes it again before the first back-end save returns.
onCellValueChanged: (event) => {
if (event.oldValue === event.newValue) {
return;
}
try {
// apiUpdate(event.data)
}
catch {
event.node.data[event.colDef.Field] = event.oldValue;
event.node.setDataValue(event.column, event.oldValue);
}
}
By changing the value back on node.data first, when setDataValue() triggers the change event again, oldValue and newValue are actually the same now and the function returns, avoiding the rather slow infinite loop.
I think it's because you change the data behind the scenes directly without agGrid noticing with node.data = , then make a change that agGrid recognises and rerenders the cell by calling setDataValue. Thereby tricking agGrid into behaving.
I would suggest a slightly better approach than StangerString, but to credit him the idea came from his approach. Rather than using a test of the oldValue/newValue and allowing the event to be called twice, you can go around the change detection by doing the following.
event.node.data[event.colDef.field] = event.oldValue;
event.api.refreshCells({ rowNodes: [event.node], columns: [event.column.colId] });
What that does is sets the data directly in the data store used by aggrid, then you tell it to refresh that grid. That will prevent the onCellValueChanged event from having to be called again.
(if you arent using colIds you can use the field or pass the whole column, I think any of them work)

Firebase child nodes are randomly deleted

Firebase child nodes are randomly deleted in random times
I have a firebase project with a lot of children per node (in the range of 100k+) and it uses real time database. The problem is, sometimes, completely randomly, few child items simply get removed.
For example, given the following database structure:
**Project** (root)
users
user1
user2
user3
.
.
.
user100000
one of the nodes among user1, user2... user100000 would just randomly get removed. This happens so randomly out of nowhere, I cannot reproduce it, and only affects less than 1% of the time, so out of more than 100k children, only 200ish would disappear in the span of 3 month. I went through every line of the code, but there is no function that removes a node. I only use updateChildValues, observe(.childAdded..., observeSingleEvent, and queryOrdered. There is no single method that starts with remove. Any ideas on why this is happening?
My first intuition is I am calling some write API like setValue or updateValue with Nil, but I am on Swift and that is not possible. I also looked at Firebase Web update() deletes all other child nodes, but this actually removes every child other than the one being added, which is definitely not happening to me. Has anyone also experienced this issue? Any help would be really appreciated. Thank you.
-----EDIT-----
Here are some example APIs that I use with write function. item variable is a dictionary defined by let.
let databaseRef = Database.database().reference()
newReferece = databaseRef.child("users").child(UID)
newReferece.setValue(item)
Another one:
theAPI.REF?.updateChildValues(dict, withCompletionBlock: { (error, ref) in
if error != nil {
onError(error!.localizedDescription)
} else {
onSuccess()
}
})

How do you save multiple fields to one row or objectId in Parse using Swift?

I'm getting the above layout from Parse. What I want is vid 1, 2, and 3 to be in the same row; associated with same object ID. How can I do this? My ultimate goal is to easily retrieve 10 video dictionary's per user on a table view. Will any of this make a difference? I'm saving like this.....
videoDict = ["id":videoId, "title":vidTitleText, "description":vidDescription, "image":vidIMG]
let videoSave = PFObject(className:"UserVideos")
videoSave["user"] = PFUser.currentUser()!.username
videoSave["userObjectId"] = PFUser.currentUser()!.objectId
videoSave["vid\(saveValueLBL.text!)"] = videoDict
videoSave.saveInBackgroundWithBlock { (success, error ) -> Void in
if success == true
{
print("Succesfull")
}
}
Where you have let videoSave = PFObject(className:"UserVideos") you are creating a new videoSave object each time. you need to move that outside of your loop so that you're accessing the same object each time instead of making a new one. However, the way you currently have your code set up you'll run into problems, because each object can only have one synchronous action called on it (in this case, your save), so the second, third, maybe even all the way to the 10th save may not occur because it needs the first one to finish before the next one can be called. You need to create your object outside your loop, run the loop, then call the save at the end to make sure it isn't saving until all of the data is updated.
If this isn't all inside of a loop, you need to get the videoSave object back each time, perhaps by storing it onto your user, and then fetching it from the user object.
Put everything outside the loop and keep just the code below inside the loop:
videoDict = ["id":videoId, "title":vidTitleText, "description":vidDescription, "image":vidIMG]
videoSave["vid\(saveValueLBL.text!)"] = videoDict
From what I understand although I saved information in Parse as a Dictionary this is in fact an invalid data type. That's why I'm having trouble retrieving because Parse doesn't recognize the info.

ResearchKit: How to get pedometer data (step count specifically) from ORKOrderedTask.fitnessCheckTaskWithIdentifier result

I added the ORKOrderedTask.fitnessCheckTaskWithIdentifier Task and it renders find in the UI. But unlike other simpler tasks containing scale/choice/date questions, I was not able to find the exact way to read the sensor data collected via ORKOrderedTask.fitnessCheckTaskWithIdentifier.
I have used the following:
private var walkingTask : ORKTask {
return ORKOrderedTask.fitnessCheckTaskWithIdentifier("shortWalkTask", intendedUseDescription: "Take a short walk", walkDuration: 10, restDuration: 5, options: nil)
}
upon task completion the task view controller delegate below is hit.
//ORKTaskViewControllerDelegate
func taskViewController(taskViewController: ORKTaskViewController, didFinishWithReason reason: ORKTaskViewControllerFinishReason, error: NSError?)
is there a way to drill down into the result object contained in task view controller (taskViewController.result) to get the step count? Or will i have to go through health kit or something and then query the required observation? Request help from anyone who has used this task before and can provide some input on how to fetch the pedometer data (step count specifically) for the duration the task was active?
I'm using swift.
The step count is not reflected in the result objects per se. Instead, one of the child ORKFileResult objects, generated from the pedometer recorder, will contain the pedometer records queried from CoreMotion, serialized to JSON.
However, exposing the step count on a result object, sounds like a useful extension / improvement, and we should see if it generalizes to other recorders too. Please open an issue on GitHub and we will see what we can do!