In my AppDelegate's applicationDidEnterBackground() I call cocos2d::Director::getInstance()->stopAnimation() and in applicationWillEnterForeground() I call cocos2d::Director::getInstance()->startAnimation().
But applicationDidEnterBackground can be called with the game still visible on screen, and not only when the game is minimized with the home button (and becomes invisible), for example when you start a buy process in your game, and google billing shows it's popup. If you now rotate the screen with this popup active, the game screen goes all black and the result is a black screen with only the google billing dialog visible.
The same happens if the google billing dialog is visible, and you minimize the app and then bring it back.
The only way I can fix this, is by NOT calling stopAnimation.
Does this have a negative impact on battery life, or will cocos2d-x automatically pause all actions while minimized?
The only way I can see how this can be fixed properly, is by knowing whether or not the game is still visible when it's being put in the background, which I could do by overriding the onStop() function on Android.
So how did others solve this?
Did you chose to stop the animation and see the black screen as a minor side effect, or do you leave the animation running?
Pause/Resuming the Director has the same effect btw...
Any help would be greatly appreciated!
Kind regards,
Mich
If your game/app enters background mode, its Activity will stop update, so all OpenGL draw calls will be hold no matter you called stopAnimation() or not. So it won't affect battery life if your game doesn't have any background job to do.
Furthermore, why screen goes black when you called stopAnimation()? Let's read some code:
void CCDisplayLinkDirector::mainLoop(void)
{
if (m_bPurgeDirecotorInNextLoop)
{
m_bPurgeDirecotorInNextLoop = false;
purgeDirector();
}
else if (! m_bInvalid)
{
drawScene();
// release the objects
CCPoolManager::sharedPoolManager()->pop();
}
}
void CCDisplayLinkDirector::stopAnimation(void)
{
m_bInvalid = true;
}
If stopAnimation() was called, it will also stop OpenGL draw call. The last frame of your game will still be shown, but if your game enters background or another Activity or something pops at front, since there's no new draw call updates the game's Activity, it will become whole black.
Related
The title pretty much explains what I want to do. I have an app where the init state has async methods to be called. There are also buttons in the build which when pressed execute async methods (api calls).
The problem is that if I navigate to a new screen before the previous screen has completed fully loading, the app shows ambigious behaviour like not loading the next screens completely or crashing altogether.
My question is that what happens to async methods of the previous screen when you navigate to a new screen before they finished executing?
Is there a way to pause/cancel the execution of those async methods when you navigate to a new screen and resume/reload when you come back to that screen?
OR
Is there a way to only navigate to the new screen when the previous screen has completed fully loading.
If you navigate from screen 1 to screen 2 using Navigator.push(...); then screen 1 state loading should finish loading your API state/data even with screen 2 being displayed. But if you use Navigator.pushReplacement(...); to navigate from screen 1 to screen 2, then the loading of your screen 1 state should be stopped, as in the hierarchy your screen has been replaced by screen 2.
If you want to call a function only when your screen state 1 has fully loaded you can use:
void initState() {
super.initState();
WidgetsBinding.instance
.addPostFrameCallback((_) => myFunction());
}
If you navigate to next screen without waiting, it still running until finish. All normal mobile apps have something called loading widget. It appears when you start to call async method & disappears when it finish. For me, I often use Stack for any screen, so it would prevent users from pressing anything on their phone. If you want to wait util move to next screen, use await.
I have noticed a few posters on Stack Overflow attempting this, but my situation is a little different. I have a Login class and when the user authenticates, I take declare an Intent to them to my NavDrawer class, which serves as the Main Activity. I am using a Map Fragment to show a Google Map, but when the user logs in for the very first time, the blue dot ( call to setMyLocationEnabled = true) is not visible. This is due to the Map loading in the background before the location permission request is loaded. Of course, after you grant permission and restart the app manually, the blue dot is visible as permission is already granted.
I am trying to build my logic so that as soon as the user logs in, they must grant permission before the map will load. That way, the blue dot will be enabled.
Apparently this is a known bug: https://issuetracker.google.com/issues/73122459
And one suggestion in the link above is to request permission in Activity before calling setContentView. I attempted to do this, but my Map is still loading in the background. Since location is integral to the app, I was thinking I could throw up a screen that prevents the map from being shown, that takes them to their settings. Then when they return, the map will be visible.
I like how LimeBike does it, with a transparent map behind it, so you still see what should be there. Once you navigate to your settings and enable location and return, their map loads and the blue dot pops up. Does anyone have experience implementing something similar?
If showing blue dot is so essencial on first launch before permissions granted - why not trying to request permission on preivious screen. Right on Login screen. And to not disturb users too early - do it after user successfully logins.
i.e.
1. User logs in
2. User stays on Login screen, sees popup with permission
3. Accepts or declines - either way navigates to Map screen
put that permissionns code into the onstart , only after permission success call on createview.
If your app flow allows you to request the location permission before showing the MapView, that's the simplest solution. You might also potentially be able to use a static map image in the background on your first launch, and then switch over to the screen with the real map after the permission request, depending on what you need to show there.
Alternatively, if you want or have to have the MapView visible behind the permission-request dialog, I've found that you can take one of two approaches to restart the MapView, whereupon it will properly get location updates in the "My Location" layer (the blue dot). You do this from onRequestPermissionsResult, if permission has been granted.
1. Replace the MapView
I placed the MapView, by itself, in a FrameLayout. First save the state of the existing MapView and shut it down:
val oldMapState = Bundle()
with(mapView) {
onSaveInstanceState(oldMapState)
onPause()
onStop()
onDestroy()
}
Then create a new MapView, run it up through to "resumed" state, and swap it in to the FrameLayout, in place of the old map view:
val mapContainer = branchMapView.parent as FrameLayout
val newBranchMapView = MapView(mapContainer.context).apply {
onCreate(oldMapState)
getMapAsync { it.isMyLocationEnabled = true }
onStart()
onResume()
}
There will be a flash of background while the new MapView loads, but I haven't found it too bothersome. One could probably refine this further to hide the new MapView for a couple of seconds, while it loads, but I don't know of a way to monitor the loading progress, so it would never be perfect; and you would run the risk of the interrupting the user manipulating the old map before you cut over.
2. Restart the MapView
The second approach is similar, but instead of creating a new MapView, we just restart the same one. As far as I can tell, calling onCreate again, after onDestroy seems to have no ill effects, except that the background goes solid black for a moment, for some reason, which I find doesn't look as good. It also wouldn't allow you to do any overlapped loading of the new map, if you wanted to try to minimize the visual disruption. However, from a code perspective, it seems like a slightly simpler solution.
If you are using a MapFragment, perhaps you can do something similar, removing the old fragment and adding a new one dynamically. (Not sure if re-use is possible; I have only tested with a MapView.)
I've got a fragment where I'm recording audio, I want to pause the recording when a user presses the home key, but continue to record on a rotation.
In onPause, onStop or some other method that gets called, how can I differentiate between when the user has pressed home, or whether the fragment is being closed down because of a rotate?
I've looked into listening for the home key press, but everything I find says that isn't supported/won't work/can't do it.
I've tried using an OrientationEventListener, but it seems to get called nonstop, and if I'm looking for 0, 90, 180, and 270, my code still gets run if orientation goes from 1 to 0.
There has to be some way to differentiate that!? Any ideas?
EDIT is this my best option? -->
http://developer.android.com/guide/topics/resources/runtime-changes.html#HandlingTheChange
EDIT
When I press the home button, I need to stop recording in onPause or onStop. When I rotate the device, I need to continue recording through those methods. So I need something I can check in those methods or before that tells me whether this is a home press or a rotation.
SOLUTION
I ended up just locking the orientation of the fragment when it's started and not allowing rotation. Not ideal, but more ideal than any other solution.
I have a 'loading' dialog which is basically a very simple splash-screen that just display a 'loading' graphic while all the resources for my (Android) app loads.
Once it's all done, the dialog is dismissed. Everything works great. Apart from one thing....
If the user presses the 'home' key while the 'loading' dialog is displayed and then returns to the app before everything is loaded, all I get is a blank screen. It still works though.... ie, eventually, the blank screen is replaced by my app.
So, why doesn't my dialog 're-display'? I've confirmed that I'm returning to my app before the dialog is dismissed, so I really don't understand it. At all other points in my game it returns with the screen exactly as it was if paused and immediately relaunched.
I'm creating my dialog like so this code is in onCreate():
load_dialog = new Dialog(MainActivity.this, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
load_dialog.setCancelable(false);
I then show it like so (also in onCreate())
//Set and display splash screen view
load_dialog.setContentView(splash);
load_dialog.show();
Once everything has loaded, in my GLRenderer classes' onSurfaceCreated() method, I simply dismiss it...
load_dialog.dismiss;
Any ideas?
This is the normal behaviour as your activity is paused when you hit home.
Make your loading_dialog global. Then inside of onResume() call:
if (load_dialog != null && !load_dialog.isShowing())
load_dialog.show();
Also remember that your activity will be re-created even if while loading you rotate the screen. So you need to watch for that too.
I am developing a small app which shows passwords of the user through a Dialog screen.
When home button is pressed, I need to dim the screen (on the multi tasking window) so that any other person cannot see the password.
When user re-opens the app, it asks an application lock. But if the user leaves the password Dialog open and presses the home button, dialog and the password which user last looked at stays visible (on the multi tasking window) for a while (3-4 seconds!!) until a new dialog asks the lock.
So far I tried ever possible dialog.dissmiss() options. Dialog dismisses only when app is opened again (until a new lock dialog appears) even I put dismiss() in onPause, onStop etc.
Any idea appreciated.
I also tried,
android.os.Process.killProcess(android.os.Process.myPid());
this.finish();
System.exit(0);
none of them actually worked.
Suggestion 1: Double-check your implementation. Tying your dialog to the activity lifecycle seems like a good idea (especially to avoid leaked window errors as described here)
The following example works out well for me (with coachMark being derived from Dialog)
#Override
protected void onResume()
{
log.debug("onResume");
super.onResume();
// Show the coachMark depending on saved preference values
coachMark.mayBeShow();
}
#Override
protected void onPause()
{
log.debug("onPause");
// Hide the coachMark if it is showing to avoid leakedWindow errors
coachMark.maybeHide();
super.onPause();
}
onPause definately gets called when you press the home button, so if this approach does not work for you, try not recreating the dialog in the restarting part of the acitivty lifecycle (onRestart(), onStart() and onResume()) and see, if it gets dismissed correctly.
Suggestion 2: Should all of the above fail, you might consider overriding the home button as described here. I highly advise against it though, since this may cause the app to work in an way that the user does not expect it to.