Player in game stopped playing - ios

I'm working on a game in iOS, which needs an internet connection. The problem I'm having is setting a Bool on the server (parse.com) when the player stops playing. Right now I'm doing this...
-(void)applicationDidEnterBackground:(UIApplication *)application
{
NSLog(#"Player logged out");
[self playerLoggedOut];
}
and
-(void)playerLoggedOut
{
PFUser *currentUser = [PFUser currentUser];
if (currentUser) {
[currentUser setObject:[NSNumber numberWithBool:NO] forKey:#"playing"];
[currentUser saveEventually];
[sharedInstance requestSentWithDesc:#"Player logged out"];
}
}
But that doesn't seem to be working, there's also the situation of the game crashing, when I believe the above would never get called.
There's also...
-(void)applicationWillTerminate:(UIApplication *)application
But I'm not sure when's that called.
There is the possibility of using a timer, but if the player never logs back in, or doesn't for a long time I'm not sure how a timer could be used.

For your current processing you would need to start a background task (see here) which starts before you make the request and ends in the completion block (so change the save method you use).
For the other cases this won't work and you may want to add some kind of 'keep-alive' message an / or parse cloud code which monitors the user interaction and automatically logs them out if they are inactive for a set time.

If your game is crashing, you must get any logs, for example:
Or something else. You can add NSLog(#"worked to the bitter end!"); at the end of your -(void)playerLoggedOut or requestSentWithDesc method.

Related

When you close the application, boolean not set to no

I use Firebase in the application. I want to record the user's status in the database, it is currently in the application or not. I use the code from the official document, which must ensure this task. When the application is opened, the value is set to NO, and then changed to YES. But at the close of the application as well as the transition to the background, the block is not called and the value does not change. Here is my code...
FIRDatabaseReference *connectedRef = [[FIRDatabase database] referenceWithPath:#".info/connected"];
[connectedRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
if([snapshot.value boolValue]) {
NSLog(#"CONNECTED");
} else {
NSLog(#"NOT CONNECTED");
}
}];
2017-01-17 13:36:38.203 Tricker[6293:817528] NOT CONNECTED
2017-01-17 13:36:40.863 Tricker[6293:817528] CONNECTED
The console comes the following information, which shows that the value changes after two seconds after the opening of the application with NO to YES. But nothing happens when the application is closed...
Prompt in what could be the problem?
You seem to be confusing connection state and application lifecycle.
When it is active, the Firebase Database client will fire information about its connection state on the .info/connected path.
But when the app is not active, the client has no way of firing this information anymore. You will need to use regular iOS application lifecycle events to detect when the app becomes inactive.
Recording the user's presence in the database works as a two-step process:
when the application starts you write a value to the database to signal that the user is online
when the application starts, you tell the database server to write another value to the database that signals that the user is gone.
Step 2 is accomplished by onDisconnectSetValue:
[presenceRef onDisconnectSetValue:#"I disconnected!"];
The trick is that you call this method early on, typically when your app starts. The write operation will be executed when the Firebase Database server detects that the client disconnected.
This disconnect can happen in two ways:
when the client closes the connection explicitly
when the the socket that the server uses to communicate with the client times out
When your app crashes, you're in situation 2. In that case it can take a few minutes before the server detects that the client is gone, since you're waiting for a socket to time-out.
You close the app means ? two things possibility
User can put app in Background :
Here you can set a NSUSEDefault value like :
(void)applicationDidEnterBackground:(UIApplication *)application {
[[NSUserDefaults standardUserDefaults] setBool:NO
forKey:#"YOUR_KEY"]; [[NSUserDefaults standardUserDefaults]
synchronize];
}
User can remove app from background :
(void)applicationWillTerminate:(UIApplication *)application {
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:#"YOUR_KEY"];
[[NSUserDefaults standardUserDefaults] synchronize];
}

NSTimer Logic Failing Somewhere

I've been able to reproduce a defect in our app twice, but most times I fail. So I'm trying to understand what could possibly be going on here and hopefully have some new things to try. Our app times out and logs the user out after 10 minutes using an NSTimer. Every time the screen is touched the timer is reset, this all works great.
When the user backgrounds the app and comes back, the following code gets called:
- (BOOL)sessionShouldTimeOut {
if (self.timeoutManager) {
NSTimeInterval timeIntervalSinceNow = [self.timeoutManager.updateTimer.fireDate timeIntervalSinceDate:[NSDate date]];
if (timeIntervalSinceNow < 0) {
return YES;
} else {
return NO;
}
}
return NO;
}
- (void)timeoutIfSessionShouldTimeOut {
if ([self sessionShouldTimeOut]) {
[self.timeoutManager sendNotificationForTimeout];
}
}
This (I suspect) is the code that's failing. What happens when it fails is the user logs in, hits the home page and locks their phone. After 10+ minutes, they unlock and the app isn't logged out. When they come back, it's the code above that gets executed to log the user out, but in some scenarios it fails - leaving the user still on the homepage when they shouldn't be.
Here's my current theories I'm trying to test:
The timer somehow fires in the background, which then runs the logout routine, but since we're in the background the UI isn't updated but the timer is invalidated (we invalidate the timer after logout) I'm not sure if UI code called from the background will be shown after the app is in the foreground, so this may not be a possibility.
The user actually is coming back a few seconds before the timer fires, then after a few seconds when it should have fired it doesn't since it was backgrounded for 10 minutes. Do timers continue to hit their original fire time if the app goes to the background?
Somehow, while in the background, self.timeoutManager, updateTimer, or fireDate are being released and set to nil, causing the sessionShouldTimeOut method to return NO. Can variables be nilled in the background? What would cause them to if they could be?
The logout routine gets run while the phone is taking a while to actually move to the app, potentially causing the UI updates to not be reflected?
I'm very open to other theories, as you can see a lot of mine are very very edge case since I'm not sure at all what's happening.
I'd appreciate any guidance anyone can offer as to what else I may be able to try, or even any insights into the underworkings of NSTimer or NSRunLoop that may be helpful in this scenario (the documentation on those is terrible for the questions I have)
In AppDelegate.h set
applicationDidEnterBackground:
UIBackgroundTaskIdentifier locationUpdater =[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:locationUpdater];
locationUpdater=UIBackgroundTaskInvalid;
} ];
This tells the os that you still have things going and not to stop it.

UISwitch latency between taps caused by Firebase

I have a view with a bunch of UISwitch.
My problem is that when I tap on a switch I need to wait about 10 seconds before being able to tap any other switch of the view.
Here is my code :
-(void) didTapSwitch:(UISwitch *)sender
{
NSLog(#"BEGIN didTapSwitch, %#",sender);
DADudesManager *dudesManager = [DADudesManager getInstance];
DADude *updatedDude = [dudesManager.dudesList objectAtIndex:[[self.spendingDudesTableView indexPathForCell:sender.superview.superview] row]];
DAAccountManager *accountManager = [DAAccountManager getInstance];
[accountManager.accountsOperationQueue addOperationWithBlock:^{
NSLog(#"BACKGROUND OPERATION BEGINS switchDudeBeneficiates, %#",sender);
DASpendingsManager *spendingsManager = [DASpendingsManager getInstance];
[[spendingsManager.spendingObserver childByAppendingPath:self.spending.spendingID] updateChildValues:#{updatedDude.dudeName: [sender isOn] ? #"1" : #"0"}];
NSLog(#"BACKGROUND OPERATION ENDS switchDudeBeneficiates, %#",sender);
}];
NSLog(#"END switchDudeBeneficiates, %#",sender);
}
My spendingObserver is a Firebase object initiated before.
When the code above is executed, the NSLogs show almost instantaneously in the console, the data is updated online at the same time, but the switches don't react to any tap for another 9 to 11 secs.
Of course commenting the line [[spendingsManager.spendingObserver childByAppendingPath:self.spending.spendingID] updateChildValues:#{weakDude.dudeName: [weakSwitch isOn] ? #"1" : #"0"}]; removes the latency, so the problem must come from Firebase, but I have no clue what's going on.
I am probably missing something obvious as I'm pretty new to IOS development !
I can think couple of reasons.
You are sending the PayLoad in the main thread, which is causing the User INterface events to be suspended.
The code you ran, might be linked to other functions in the library you are using, that might be causing the lag.
TRY - >
try putting your code in an NSOperation and execute that. Or use GCD to do work on different thread just not the UI thread which is the main thead.
Step back and simplify. Make your switch code simply log the change in value. NSLog includes a timestamp, so you can tell when the switch events occur.
If do-nothing code responds quickly, as I suspect it will, then add log statements at the beginning and end of your switch action method. That way you can see if there is a delay between the beginning and end of the processing.
You could also run the app in instruments (time profiler) and see where your app is spending time.

Firebase PresenceManaging iOS

I'm implementing a system based on firebase docs:
[connectionMonitor observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
if([snapshot.value boolValue]) {
// connection established (or I've reconnected after a loss of connection)
// add this device to my connections list
// this value could contain info about the device or a timestamp instead of just true
Firebase * con = [[Firebase alloc]initWithUrl:[NSString stringWithFormat:#"%#Users/%#/connections/", urlString, currentUserId]];
Firebase * newConnection = [con childByAutoId];
[newConnection setValue:#YES];
// when this device disconnects, remove it
[newConnection onDisconnectRemoveValue];
}
}];
Which works fine, if the user fully disconnects, but that's my problem.
I use this system to see if the user is online. If they're not online, I trigger a push notification. If the user closes the app, firebase doesn't disconnect, but it also doesn't receive updates, so on the other end, the user looks like they are still online. For the firebase onDisconnect value to properly set, the user is required to completely close out of the app.
I have resolved this by adding:
- (void)applicationWillResignActive:(UIApplication *)application
{
[Firebase goOffline];
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[Firebase goOffline];
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[Firebase goOnline];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[Firebase goOnline];
}
Is this normal behavior, or am I doing something wrong?
This is (currently) expected behavior. Firebase won't trigger the presence actions until the client actually disconnects and iOS will leave the underlying socket connection alive for some period of time (probably less than 5 minutes) after the app goes to the background... so presence will be delayed. It should still definitely happen eventually though.
Your workaround should work fine, or to avoid tearing down the whole connection, you could just set the presence bit to #NO / #YES on going to background / foreground.
I can see how most apps would expect presence to kick in when the app goes to the background, so we may investigate changing this behavior in the future.

game center sandbox -- neither truly logged in nor out

I am working on a new version of my app. I was using the sandbox normally for a while, but now all of my devices are stuck with a very strange problem. They appear to be halfway logged into game center. It doesn't work for them, but they can't log out either. Here is my authentication method:
- (void)authenticateLocalPlayer {
GKLocalPlayer* localPlayer = WJLocalPlayer;
WJLog(#"Authenticating local user...");
if (localPlayer.authenticated == NO) {
localPlayer.authenticateHandler = ^ (UIViewController* vc, NSError *error) {
if (error) {
WJLog(#"Authentication failed! %#", [error localizedDescription]);
}
else {
WJLog(#"Authentication succeeded!");
NSString* name = [GKLocalPlayer localPlayer].displayName;
WJLog(#"display name is %#", name);
NSString* alias = [GKLocalPlayer localPlayer].alias;
WJLog(#"alias is %#", alias);
GKTurnBasedEventHandler *ev = [GKTurnBasedEventHandler sharedTurnBasedEventHandler];
ev.delegate = self;
}
};
}
}
And here is what I am seeing from the log statements [WJLog is just my own version of NSLog without the garbage]:
Authenticating local user...
Authentication succeeded!
display name is Me
alias is (null)
I can log in or out in the game center app. It makes no difference. I always see the above. I even tried restoring one of the devices to factory settings. The result was still the same. I also tried disabling and re-enabling game center for the new version of my app. Still the same result.
Any ideas?
You're completely ignoring the UIViewController parameter. You're supposed to present this to the user if it exists, so they can log in. Probably you are only now experiencing this because you logged in to the non-sandbox game center, and now when you run the app it wants to ask you for your sandbox credentials, but instead you're assuming you're authenticated.
You have some other problems here too:
You should set the authenticateHandler once only, soon after your app launches.
You should check localPlayer.authenticated inside your authenticateHandler, and nowhere else, as this is the only place it's guaranteed to be valid. Specifically, it's a meaningless value after you resume from the background and until your authenticateHandler gets called again. If you need it elsewhere, use a global variable that gets initialised to false at startup and also in your applicationWillEnterForeground method, and only gets set to true inside your authenticateHandler when you've determined that the localPlayer is actually authenticated.
Check the error and log it by all means, but it doesn't tell you anything about whether authentication actually succeeded, so remove the 'else'.
Have a look at the documentation here.

Resources