Save 2 different PFObjects eventually or fail both - ios

Using Parse SDK for iOS, I have 2 tables :
Game
- UserA : Pointer <_User>
- UserB : Pointer <_User>
- Round : Number
- IsTurnOfUserA : Bool
RoundScore
- GameId : Pointer <Game>
- User : Pointer <_User>
- Score : Number
- etc
A game is done in 3 rounds between 2 users.
When a user ends a round, it toggles Game.IsTurnOfUserA and saves the score for the round to RoundScore table.
In iOS, I didn't find a way to update Game table AND save a RoundScore eventually (maybe later if there is no network).
Both must be done or none at all, but I don't want to end up with only one of the 2 query to be successful and the other one failed.
With Cloud Code, it should be easy to do so but there is no call eventually function.
Update: Maybe there is something to try with Parse's local database ? But I don't know that tool yet.
Important: RoundScore has a field that depends on Game. If Game Object is new, it doesn't have an ObjectId yet, but I still need to link it to the RoundScore Object.

Unfortunately it isn't possible with saveEventually.
What you would need to do is implement your own network checking code and call a cloud method that will save both. That would be the best option.
A hack you could try as an alternative is to save the combined data to another class and have a background job on the server turn that single temporary row into a row in each table, then remove the temporary row.
The drawbacks of this hack is that the background job can run every 15 minutes only, so there might be up-to 15 minutes delay. It also adds extra complexity and overhead to your app.

As Timothy Walters suggested, here is the hack without any background job:
I created a fake table that has the columns of both tables Game and RoundScore
GameAndRoundScore
- Game_UserA
- Game_UserB
- RoundScore_GameId
- RoundScore_Score
- etc
In Cloud Code, I added this function before save
Parse.Cloud.beforeSave("GameAndScoreRound", function(request, response) {
var Game = Parse.Object.extend("Game");
var game = new Game();
game.set("UserA", request.object.get("game_UserA"));
game.set("UserB", request.object.get("game_UserB"));
game.save.then(function(newGame) {
var RoundScore = Parse.Object.extend("RoundScore");
var roundScore = new RoundScore();
//roundScore.set(...)
return scoreRound.save();
}).then(function(newRoundScore) {
response.success();
});
});
Then for the fake table data, I can either leave it as it is or set a background job that empties it or even empty the table manually on the Parse Backend.

Related

How to create negative Firebase timestamp in swift

For an iOS app I am working on, I need to fetch messages in descending order i.e the latest message comes first, followed by second newest message etc.
From looking at other SO answers and research it seems that the best approach for my situation is to create a negative timestamp and then persist that to the database as an extra property to messages.
I will then use queryOrderedByChild('negativeTimestamp') to fetch the messages in a observeSingleEvent and then have a childAdded observer to handle messages which are sent once initial calls are made.
In the firebase documentation it says I can get the server timestamp value from this snippet of code firebase.database.ServerValue.TIMESTAMP
How do I write this for Swift 3?
First, see the linked answer in the comments. That answer relies on the client to generate a timestamp that's made negative and written to Firebase.
If you want to have Firebase generate a timestamp, that can be done as well with this little snappy Firebase structure and piece of code.
First, let's take a look at the structure
root
parent_node
-Y8j8a8jsjd0adas
time_stamp: -1492030228007
timestamp: 1492030228007
Next, some code to create and work with that structure:
Define a var we can use within our class that references the Firebase time stamp
let kFirebaseServerValueTimestamp = [".sv":"timestamp"]
and a function that adds an observer to the timestamp node:
func attachObserver() {
let timestampRef = self.ref.child("timestamp")
let parentNodeRef = self.ref.child("parent_node")
var count = 0
timestampRef.observe(.value, with: { snapshot in
if snapshot.exists() {
count += 1
if count > 1 {
let ts = snapshot.value as! Int
let neg_ts = -ts
let childNodeRef = parentNodeRef.childByAutoId()
let childRef = childNodeRef.child("time_stamp")
childRef.setValue(neg_ts)
count = 0
}
}
})
And a function that writes out a timestamp, therefore causing the observer to fire which creates child nodes within the parent_node based on the Firebase time stamp
func doTimestamp() {
let timestampRef = self.ref.child("timestamp")
timestampRef.setValue(kFirebaseServerValueTimestamp)
}
Here's the rundown.
In the attachObserver function, we attach an observer to the timestamp node - that node may or may not exist but if it doesn't it will be created - read on. The code in the closure is called any time an event occurs in the timestamp node.
When the doTimestamp function is called, it creates and writes a timestamp to the timestamp node, which then fires the observer we attached in attachObserver.
The code in the observe closure does the following:
Make sure the snapshot contains something, and if it does, increment a counter (more on that in a bit). If the counter is greater than 1 get the timestamp as an integer from the snapshot. Then, create it's negative and write it back out to Firebase as a child of parent_node.
How this would apply would be anytime you want to timestamp a child node with a Firebase generated timestamp but negative value for reverse loading/sorting - which speaks to the OP question.
The gotcha here is that when this happens
timestampRef.setValue(kFirebaseServerValueTimestamp)
It actually writes twice to the node, which would cause the code in the closer to be called twice.
Maybe a Firebaser can explain that, but we need to ignore the first event and capture the second, which is the actual timestamp.
So the first event will cause the observer closer to fire, making count = 1, which will be ignored due to the if statement.
Then the second event fires, which contains the actual timestamp, and that's what we use to make negative and write out to Firebase.
Hope this helps the OP and the commenters.
Regardless whether it's for Swift or not, another conceptual solution is to rely on Firebase's server time offset value.
It's not as precise as firebase.database.ServerValue.TIMESTAMP, but the difference is usually within milliseconds. The advantage is that it lets you create a negative timestamp on the client without having to update your Firebase node twice.
You grab the server time offset value when you need it from Firebase, generate the negative timestamp on the client, and then save your object in Firebase once.
See:
https://groups.google.com/forum/#!topic/firebase-talk/EXMbZmyGWgE
https://firebase.google.com/docs/database/ios/offline-capabilities#clock-skew (for iOS).
https://firebase.google.com/docs/database/web/offline-capabilities#clock-skew (for web).
var offsetRef = firebase.database().ref(".info/serverTimeOffset");
offsetRef.on("value", function(snap) {
var offset = snap.val();
var negativeTimestamp = (new Date().getTime() + offset) * -1;
});

FEventType.ChildAdded event only fired once

my firebase data structure looks like the following
user
|__{user_id}
|__userMatch
|__{userMatchId}
|__createdAt: <UNIX time in milliseconds>
I'm trying to listen for the child added event under userMatch since a particular given time. Here's my swift code:
func listenForNewUserMatches(since: NSDate) -> UInt? {
NSLog("listenForNewUserMatches since: \(since)")
var handle:UInt?
let userMatchRef = usersRef.childByAppendingPath("\(user.objectId!)/userMatch")
var query = userMatchRef.queryOrderedByChild("createdAt");
query = query.queryStartingAtValue(since.timeIntervalSince1970 * 1000)
handle = query.observeEventType(FEventType.ChildAdded, withBlock: { snapshot in
let userMatchId = snapshot.key
NSLog("New firebase UserMatch created \(userMatchId)")
}, withCancelBlock: { error in
NSLog("Error listening for new userMatches: \(error)")
})
return handle
}
What's happening is that the event call back is called only once. Subsequent data insertion under userMatch didn't trigger the call. Sort of behaves like observeSingleEventOfType
I have the following data inserted into firebase under user/{some-id}/userMatch:
QGgmQnDLUB
createdAt: 1448934387867
bMfJH1bzNs  
createdAt: 1448934354943
Here are the logs:
2015-11-30 17:32:38.632 listenForNewUserMatches since:2015-12-01 01:32:37 +0000
2015-11-30 17:45:55.163 New firebase UserMatch created bMfJH1bzNs
The call back was fired for bMfJH1bzNs but not for QGgmQnDLUB which was added at a later time. It's very consistent: after opening the app, it only fires for the first event. Not sure what I'm doing wrong here.
Update: Actually the behavior is not very consistent. Sometimes the call back is not fired at all, not even once. But since I persist the since time I should use when calling listenForNewUserMatches function. If I kill the app and restart the app, the callback will get fired (listenForNewUserMatches is called upon app start), for the childAdded event before I killed the app. This happens very consistently (callback always called upon kill-restart the app for events that happened prior to killing the app).
Update 2: Don't know why, but if I add queryLimitedToLast to the query, it works all the time now. I mean, by changing userMatchRef.queryOrderedByChild("createdAt") to userMatchRef.queryOrderedByChild("createdAt").queryLimitedToLast(10), it's working now. 10 is just an arbitrary number I chose.
I think the issue comes from the nature of time based data.
You created a query that says: "Get me all the matches that happened after now." This should work when the app is running and new data comes in like bMfJH1bzNs. But older data like QGgmQnDLUB won't show up.
Then when you run again, the since.timeIntervalSince1970 has changed to a later date. Now neither of the objects before will show up in your query.
When you changed your query to use queryLimitedToLast you avoided this issue because you're no longer querying based on time. Now your query says: "Get me the last ten children at this location."
As long as there is data at that location you'll always receive data in the callback.
So you either need to ensure that since.timeIntervalSince1970 is always earlier than the data you expect to come back, or use queryLimitedToLast.

online/offline data management

I have to create an application that has functionality similar to the contacts app. You can add a contact on the client's iPhone and it should get uploaded onto the client's iPad. If the client updates the contact on their iPad, it should get updated on their iPhone.
Most of this is fairly straight forward. I am using Parse.com as my back end and saving contacts locally with Core Data. The only problem I'm encountering is managing contacts when the user is offline.
Let's say I have an iPhone and an iPad. Both of them currently have the same version of the online database. My iPhone is now offline. It is 9AM.
At 10AM I update the phone number for a contact on my iPad. It saves the change locally and online. At 11AM I update the email address for the same contact on my iPhone but I'm still offline.
At noon, my iPhone connects to the internet and checks the server for changes. It sees that its changes are more recent than the latest update (checking an updatedAt timestamp property), so instead of downloading the new phone number for the contact (which is "obsolete"), it overrides the phone number along with the email address (updates the new phone number to the old version it has because it was offline during the phone number update at 10AM and its changes are supposedly more recent).
How am I supposed to manage the online/offline problems encountered such as the one above? A solution I can think of would be to keep updated timestamps on every attribute for a contact instead of just a general updatedAt property for the entire contact, e.g. when was first name updated, when was last name updated, and then manually check if an offline device has more recent changes on every attribute instead of overwriting the whole object, but that seems sloppy.
I was also thinking on having an updatedLocally and updatedOnline timestamp property on every Core Data object. This way if the two don't match I can do a diff-check and use the most recent one for conflicts but this still doesn't seem like the cleanest solution. Has anyone else encountered something similar? If so, how did you solve it?
Pseudocode/Summary for what I think? covers every test case but still isn't very elegant/complete:
2 Entities on Parse.com: Contact and Contact History
Contact has first, last, phone, email, onlineUpdate
Contact History has a Primary Key to a Contact to refer to and the same attributes but with history. e.g. first: [{value:"josue",onlineUpdate:"9AM"},{value:"j",onlineUpdate:"10AM"},{value:"JOSUEESP",onlineUpdate:"11AM"}]
1 Entity on Core Data, Contact:
Contact has first, last phone, email, onlineUpdate, and offlineUpdate (IMPORTANT: this is only on Core Data, not on Parse)
for every contact in parse database as onlineContact {
if onlineContact does not exist in core data {
create contact in core data
}
else {
// found matching local object to online object, check for changes
var localContact = core data contact with same UID as onlineContact
if localContact.offlineUpdate more recent than onlineContact.onlineUpdate {
for every attribute in localContact as attribute {
var lastOnlineValueReceived = Parse database Contact History at the time localContact.onlineUpdate for attribute
if lastOnlineValueReceived == localContact.attribute {
// this attribute did not change in the offline update. use latest available online value
localContact.attribute = onlineContact.attribute
}
else{
// this attribute changed during the more recent offline update, update it online
onlineContact.attribute = localContact.attribute
}
}
}
else if onlineContact.onlineUpdate more recent than localContact.offlineUpdate {
// another device updated the contact. use the online contact.
localContact = offlineContact
}
else{
// when a device is connected to the internet, and it saves a contact
// the offline/online update times are the same
// therefore contacts should be equivalent in this else statement
// do nothing
}
}
TL;DR: How are you supposed to structure a kind of version-control system for online/offline updates without accidental overwriting? I'd like to limit bandwidth usage to a minimum.
I would suggest to use key based updates instead of contact based updates.
You should not send the whole contact to the server, in most cases the user would just change a few attributes anyways (things like 'last name' usually don't change very often). This also reduces bandwith usage.
Along with the applied changes of your offline contact you send the
old version number/last update timestamp of your local contact to the server. The server can now
determine whether or not your local data is up to date, simply by looking at your old version number. If your old version number matches the current version number of the server there is no need for your client to update any other information. If this is not the case the server should send you the new contact (after applying your requested update).
You can also save those commits, this would result in a contact history
which does not store the whole contact each time a key was changed but only the changes themselves.
A simple implementation in pseudo code could look like this:
for( each currentContact in offlineContacts ) do
{
if( localChanges.length > 0){ // updates to be made
commitAllChanges();
answer = getServerAnswer();
if(answer.containsContact() == true){
// server sent us a contact as answer so
// we should overwrite the contact
currentContact = answer.contact;
} else {
// the server does not want us to overwrite the contact, so we are up to date!
}
// ...
}
} // end of iterating over contacts
The server side would look just as simple:
for (currentContactToUpdate in contactsToUpdate) do
{
sendBackContact = false; // only send back the updated contact if the client missed updates
for( each currentUpdate in incomingUpdates ) do {
oldClientVersion = currentUpdate.oldversion;
oldServerVersion = currentContact.getVersion();
if( oldClientVersion != oldServerVersion ){
sendBackContact = true;
// the client missed some updates from other devices
// because he tries to update an old version
}
currentContactToUpdate.apply(currentUpdate);
}
if(sendBackContact == true){
sendBack(currentUpdate);
}
}
To get a better understanding of the workflow I will provide an example:
8 AM both clients and the server are up to date, each device is online
Each device has an entry (in this case a row) for the contact 'Foo Bar' which has the primary key ID.
The version is the same for each entry, so all of them are up to date.
_ Server iPhone iPad
ID 42 42 42
Ver 1 1 1
First Foo Foo Foo
Last Bar Bar Bar
Mail f#b f#b f#b
(excuse this terrible format, SO sadly does not support any sort of tables...)
9 AM your iPhone is offline. You notice Foo Bar's email changed to 'foo#b'.
You change the contact information on your phone like this:
UPDATE 42 FROM 1 TO 2 Mail=foo#b
// ^ID ^old version ^new version ^changed attribute(s)
so now the contact in your phone would look like this:
_ iPhone
ID 42
Ver 2
First Foo
Last Bar
Mail foo#b
10 AM your iPad is offline. You notice 'Foo Bar' is actually written as 'Voo Bar'! You apply the changes immediatly on your iPad.
UPDATE 42 FROM 1 TO 2 First=Voo
Notice that the iPad still thinks the current version of contact 42 is 1. Neither the server nor the iPad did notice how you changed the mail address and increased the version number, since no devices were connected to the network. Those changes are only locally stored and visible on your iPad.
11 AM you connect your iPad to the network. The iPad sends the recent update
to the server. Before:
_ Server iPad
ID 42 42
Ver 1 2
First Foo Voo
Last Bar Bar
Mail f#b f#b
iPad -> Server:
UPDATE 42 FROM 1 TO 2 First=Voo
The server can now see that you are updating Version 1 of contact 42. Since version 1 is the current version your client is up to date (no changes commited in the mean time while you were offline).
Server -> iPad
UPDATED 42 FROM 1 TO 2 - OK
After:
_ Server iPad
ID 42 42
Ver 2 2
First Voo Voo
Last Bar Bar
Mail f#b f#b
12 AM you disconnected your iPad from the network and connect your iPhone.
The iPhone tries to commit the recent changes. Before:
_ Server iPhone
ID 42 42
Ver 2 2
First Voo Voo
Last Bar Bar
Mail f#b foo#b
iPhone -> Server
UPDATE 42 FROM 1 TO 2 Mail=foo#b
The server notices how you try to update an old version of the same contact.
He will apply your update since it is more recent than the iPad's update but
will send you the new contact data to make sure you get the updated first name aswell.After:
_ Server iPhone
ID 42 42
Ver 2 2
First Voo Voo
Last Bar Bar
Mail foo#b foo#b
Server -> iPad
UPDATED 42 FROM 1 TO 3 - Ver=2;First=Voo;.... // send the whole contact
/* Note how the version number was changed to 3, and not to 2, as requested.
* If the new version number was (still) 2 the iPad would miss the update
*/
The next time your iPad connects to the network and has no changes to commit it should just send the current version of the contact and see whether it is still up to date.
Now you have committed two offline changes without overwriting each other.
You can easily extend this approach and so some optimizations. For example:
If the client tries to update an old version of the contact, don't send them the whole contact as answer. Rather send them the commits they missed and let them update their contact by themselves. This is useful if you store lots of information about your client and expect few changes to be done between updates.
If the client updated all information about a contact we can assume he does not need to know about the missed updates, however we would let him know about everything he missed (but it would/should have no effect to him)
I hope this helps.
I know nothing about iOs, core data and parse.com, so I can suggest only a general algorithmic solution. I think you can an approach similar to what is done in version control systems.
The simplest thing is to keep all the history on the server: keep all the revisions of the contact list. Now during the synchronization the phone sends information about the last server revision it has seen, and this revision will be the "common parent" for both the current phone revision and current server revision.
Now you can see what has changed on server and on phone since that revision, and apply the usual 3-way comparison: if some field has changed only on the server, then send the new field to the phone; if some field has changed only on the phone, then change it on server too, if some field has been changed both on phone and on server and the changes are different, then you have a conflict and have to ask user.
A variation of this approach might be to work with changes, not revisions. The primary data at both the server and the client will be not the contact list, but a history of its changes. (The current contact list, as well as a set of 'keyframes' can also be kept if needed; it will not be used for conflict resolving algorithm, but can be used so that it can be quickly shown and used.)
Then, when a user synchronizes the data, you download/upload only the changes. If there are any conflict changes, you have nothing left but to ask a user, otherwise you just merge them. How you define a change and which changes are considered conflicting, is up to you. A simple approach can be defining a change as a pair (field, new-value), and two change are conflicting if they have the same field. You can also employ a more advanced conflict resolving logic such as if one change changes only the first half of the email, and the other the second half, then you can merge them.
The correct way to do this is to keep a transaction log. Whenever you save in Core Data you create a log entry in your transaction log. When you are next online you play back the transaction log against the server.
This is how iCloud and other sync services work.
Instead of having separate flag for each of the core data objects, you can have separate table which will store IDs(primary key from database table which stores contact information) of all updated contacts.
Later when user comes online you just fetch those contacts from your actual contact detail table and upload them on your server.

Using ReactiveCocoa to track UI updates with a remote object

I'm making an iOS app which lets you remotely control music in an app playing on your desktop.
One of the hardest problems is being able to update the position of the "tracker" (which shows the time position and duration of the currently playing song) correctly. There are several sources of input here:
At launch, the remote sends a network request to get the initial position and duration of the currently playing song.
When the user adjusts the position of the tracker using the remote, it sends a network request to the music app to change the position of the song.
If the user uses the app on the desktop to change the position of the tracker, the app sends a network request to the remote with the new position of the tracker.
If the song is currently playing, the position of the tracker is updated every 0.5 seconds or so.
At the moment, the tracker is a UISlider which is backed by a "Player" model. Whenever the user changes the position on the slider, it updates the model and sends a network request, like so:
In NowPlayingViewController.m
[[slider rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(UISlider *x) {
[playerModel seekToPosition:x.value];
}];
[RACObserve(playerModel, position) subscribeNext:^(id x) {
slider.value = player.position;
}];
In PlayerModel.m:
#property (nonatomic) NSTimeInterval position;
- (void)seekToPosition:(NSTimeInterval)position
{
self.position = position;
[self.client newRequestWithMethod:#"seekTo" params:#[positionArg] callback:NULL];
}
- (void)receivedPlayerUpdate:(NSDictionary *)json
{
self.position = [json objectForKey:#"position"]
}
The problem is when a user "fiddles" with the slider, and queues up a number of network requests which all come back at different times. The user could be have moved the slider again when a response is received, moving the slider back to a previous value.
My question: How do I use ReactiveCocoa correctly in this example, ensuring that updates from the network are dealt with, but only if the user hasn't moved the slider since?
In your GitHub thread about this you say that you want to consider the remote's updates as canonical. That's good, because (as Josh Abernathy suggested there), RAC or not, you need to pick one of the two sources to take priority (or you need timestamps, but then you need a reference clock...).
Given your code and disregarding RAC, the solution is just setting a flag in seekToPosition: and unsetting it using a timer. Check the flag in recievedPlayerUpdate:, ignoring the update if it's set.
By the way, you should use the RAC() macro to bind your slider's value, rather than the subscribeNext: that you've got:
RAC(slider, value) = RACObserve(playerModel, position);
You can definitely construct a signal chain to do what you want, though. You've got four signals you need to combine.
For the last item, the periodic update, you can use interval:onScheduler::
[[RACSignal interval:kPositionFetchSeconds
onScheduler:[RACScheduler scheduler]] map:^(id _){
return /* Request position over network */;
}];
The map: just ignores the date that the interval:... signal produces, and fetches the position. Since your requests and messages from the desktop have equal priority, merge: those together:
[RACSignal merge:#[desktopPositionSignal, timedRequestSignal]];
You decided that you don't want either of those signals going through if the user has touched the slider, though. This can be accomplished in one of two ways. Using the flag I suggested, you could filter: that merged signal:
[mergedSignal filter:^BOOL (id _){ return userFiddlingWithSlider; }];
Better than that -- avoiding extra state -- would be to build an operation out of a combination of throttle: and sample: that passes a value from a signal at a certain interval after another signal has not sent anything:
[mergedSignal sample:
[sliderSignal throttle:kUserFiddlingWithSliderInterval]];
(And you might, of course, want to throttle/sample the interval:onScheduler: signal in the same way -- before the merge -- in order to avoid unncessary network requests.)
You can put this all together in PlayerModel, binding it to position. You'll just need to give the PlayerModel the slider's rac_signalForControlEvents:, and then merge in the slider value. Since you're using the same signal multiple places in one chain, I believe that you want to "multicast" it.
Finally, use startWith: to get your first item above, the inital position from the desktop app, into the stream.
RAC(self, position) =
[[RACSignal merge:#[sampledSignal,
[sliderSignal map:^id(UISlider * slider){
return [slider value];
}]]
] startWith:/* Request position over network */];
The decision to break each signal out into its own variable or string them all together Lisp-style I'll leave to you.
Incidentally, I've found it helpful to actually draw out the signal chains when working on problems like this. I made a quick diagram for your scenario. It helps with thinking of the signals as entities in their own right, as opposed to worrying about the values that they carry.

actionscript 2 go to another screen after variable = 10

I'm making a game where i have a character moving through a maze collecting coins. Once I have collected 10 coins i want my game to automatically go to the end keyframe. The variable Coincounter keeps a record of the number of coins the user has collected so far. So far i have this code to make my game do that....
if (_root.Coincounter == 10) {
trace("got to ten" +_root.Coincounter);
gotoAndStop(4) ;
}
The code is inside an onClipEvent - either keyDown or enterFrame, i can put it in either of them. The trace works fine so it's my gotoAndStop code that doesn't work - i'm not sure why. Thanks for the help.

Resources