I'm trying to find a way to properly handle setting up an activity where its orientation is determined from data in the intent that launched it. This is for a game where the user can choose levels, some of which are int portrait orientation and some are landscape orientation. The problem I'm facing is that setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) doesn't take effect until the activity is fully loaded. This is a problem for me because I do some loading and image processing during startup, which I'd like to only have to do once.
Currently, if the user chose a landscape level:
the activity starts onCreate(), defaulting to portrait
discovers from analysing its launching Intent that it should be in landscape orientation
continues regardless all the way to onResume(), loading information and performing other setup tasks
at this point setRequestedOrientation kicks in so the application runs through onPause() to onDestroy()
it then again starts up from onCreate() and runs to onResume() repeating the setup from earlier
Is there a way to avoid that and have it not perform the loading twice? For example, ideally, the activity would know before even onCreate was called whether it should be landscape or portrait depending on some property of the launching intent, but unless I've missed something that isn't possible. I've managed to hack together a way to avoid repeating the loading by checking a boolean before the time-consuming loading steps, but that doesn't seem like the right way of doing it. I imagine I could override onSaveInstanceState, but that would require a lot of additional coding. Is there a simple way to do this?
Thanks!
Solution:
As per Daniel's answer, this was actually quite easy to fix. I just needed to make a few small changes. In my 'menu' Activity, where the player would choose which level to play, I just had to add an if/else check to choose which class would be started by my Intent. This was done with a simple int representing portrait or landscape, determined when the player selected a level. I then created a second class extending my 'GameLogic' class; this is the class which contained most of the code for the game itself, rather than the menus, instructions, etc.
public class GameLandscape extends GameLogic{
}
Literally that simple and completely empty. That way it inherited all the code from my previous activity where I had already coded it to handle things differently depending on the orientation. Lastly I just had to add a line to the manifest stating that GameLandscape would always run in landscape, and GameLogic would always run in portrait.
So a simple problem indeed.
You could make two Activities - one for portrait levels, the other for landscape levels - and then set the Activity's orientation in AndroidManifest.xml, using the android:screenOrientation attribute. You won't even have to duplicate code if you use inheritance; use your current Activity as the base activity, and just create the landscape/portrait Activities as subclasses of that Activity.
I think a better solution would be for the Intent to open the correct Activity of these two, though if you must have everything be routed via Intent extra analysis, you could forward all levels to a third Activity that does nothing more than analyse the Intent and then forward it to the proper Activity.
You could also override onRetainNonConfigurationInstance(). This lets you temporarily store one item that you can retrieve by calling getLastNonConfigurationInstance(). That way you can load all of the stuff that you need and in your onRetainNonConfigurationInstance() method you can save it all into a data structure and return it. The in your onCreate() you can call getLastNonConfigurationInstance() and if that returns null load, load all of your stuff, if it return something, then you have it all loaded. Here's a quick example:
public class MyActivity extends Activity
{
#Override
public void onCreate(Bundle savedInstanceState)
{
DataStructure myData = (DataStructure)getLastNonConfigurationInstance();
if(myData == null)
{
// Load everything in
}
else
{
// Unpack myData
}
}
#Override
public Object onRetainNonConfigurationInstance()
{
DataStructure myData = new DataStructure();
// Put everything in to myData
return myData;
}
}
Related
After user inputs parameters in MainActivity for my app (shown below), he taps Search, which calls MatchesActivity, which generates output on a new screen (shown further below), which is exited via tapping back.
But with MatchesActivity active, every time the device is rotated, Search is again executed because the Activity restarts. In the screenshot below, I rotated device from vertical to horizontal to vertical to horizontal back to vertical.
It looks silly.
The output is generated in MatchesActivity that is invoked in onCreate in MainActivity like so:
Intent matchesIntent;
matchesIntent = new Intent(MainActivity.this, MatchesActivity.class);
startActivity(matchesIntent);
Here's the essence of onCreate for MatchesActivity:
#Override protected void onCreate(Bundle savedInstanceState)
{
MainActivity.dbc.setDbProcesslistener(this); // to know txaMatches has been defined
MainActivity.dbc.findDBMatches(); // generate output
}
I did research. I found some complicated ways of preventing an activity from restarting when the device is rotated. For example .
I'm hoping for a simpler solution. Any ideas?
As you have found, one option is to prevent the activity from being recreated on configuration changes all together. This is not always the best option, as this will prevent other things depending on the configuration from being recreated/reloaded too (e.g. resources overridden with the "-land" qualifier).
Another option is to cache the result of the DB search somehow. This could be done by adding a wrapper around your database that memorizes the term and results of the last search. Another way to cache the results would be to use a fragment, and reuse that fragment across activity recreations. Whether a fragment is recreated along with its activity is controlled by this method:
http://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean).
My solution was simple.
Introduce boolean variable outputIsShowing, set it to true in onCreate as MatchesActivity terminates, set it for false when onCreate or onResume are executed in MainActivity (i.e., when MatchesActivity terminates), and return immediately in onCreate for MatchesActivity if outputIsShowing is true.
So if MatchesActivity is active when device is rotated, outputIsShowing will be true, so don't execute again.
It may not be best practice, but I've extensively tested it under normal conditions and am happy enough so far. Not sure if anything is lurking out there as a "gotcha".
I plan to go back and study the suggestions made so far since the more general situation is definitely important. And I will have to do so if someone finds fault with what I've done.
#Override protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// usual details prior to asking for matches
if(outputIsShowing)
return;
MainActivity.dbc.setDbProcesslistener(this); // to know matches was defined
MainActivity.dbc.findDBMatches();
outputIsShowing = true;
}
* EDIT *
Strangely, after embedding TextView txaMatches in a ScrollView to accomplish smooth, accelerated scrolling, I had to remove the references to outputIsShowing in order to see output after two device orientation changes.
And now, maybe I'll submit another question to address the fact that, very infrequently after screensaver forces waking the device, the output does NOT show if that is where the focus was when screensaver became active. Tapping 'back' to get to user input screen and then immediately tapping Search restores all to normal until about 100 (give or take) screensaver instances later, the output is again missing.
Such a bug makes me think I ought to follow the advice above.
If I do, or when I figure out the problem, I'll edit this again.
I have an activity class that locks itself with a custom PIN whenever it is resumed/recreated (so that I can lend my phone to someone secure in the knowledge they can't see that app's data).
The problem this causes is that rotating the device recreates the Activity and redisplays the PIN lock, which is a bit unsmooth.
So, is there some way the Activity to know either
I am being destroyed because of an orientation change.
I am being created as a result of an orientation change.
I would like to avoid solutions based on android:configChanges="orientation|screenSize" if possible.
EDIT: For posterity, I ended up doing this by having all Activities in the app inherit from this LockableActivity class.
A very simple solution is to check the time passed between 'onPause' and 'onResume'. If it is less than 0.2 seconds then you haven't handed your phone over ...
In fact, you could make this a user controllable security feature: how long away from the activity before pin entry is needed again could be set by the user.
For the truly obsessive, you could ask the user to reorient their phone during the set up phase to determine the associated time lapse and set that as the minimum.
As noted elsewhere, you could also use onSaveInstanceState. In this approach that is when you would store the time for comparison later.
Put the below code in your activity, it will get triggered when the device is rotated
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
//perform your operations here
}
If you also want to check whether it is in landscape or portrait mode , you can apply below conditions
if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE){
}
else {
}
If your activity is normally destroyed, onSaveInstanceState() will never be called. During orientation change (or any other change that needs to load different resources), onSaveInstanceState() will be called. In this method, you can save your data that you want your activity to have during it's re-creation.
If your activity is normally created, savedInstanceState will be null. But if it was created because of re-creation (like change in orientation), then savedInstanceState will not be null and you can use this to fetch the data that you saved during onSaveInstanceState().
It's a bad idea to override onConfigurationChanged(), instead you could just use these methods that Android provide to persist your data or application logic . In your case, it could be something like this in your onCreate().
if(savedInstanceState != null){
boolean loggedIn= savedInstanceState.getBoolean("LoggedIn",false);
if(!loggedIn){
// Not logged in, hence show pinlock
}
}
You could do something like this.
I have a method in my main class, that fetches some data from the internet. The thing is that after everything is done, if I change the screen orientation by moving the device, everything starts allover again(fetching data while displaying a loading screen). Is there somewhere I could put my method so that if my device's screen orientation changes, it won't erase everything that has been done until that moment? Thanks.
What is happening to you is that every time you rotate your activity is recreated, as per android good practices you should handle your activity being recreated because android may destroy your activity at any point if resources go low on the device. Take a look at saving the state of your activity and how to restore it and the link.
Example using onSaveInstanceState()
You can use a singleton class to store your data.
If you prefer a simpler way you can also put your data as static, so the orientation change will not throw them away.
I think that your Activity is getting recreated again. In that case,it will load again.
1). You can handle orientation change by overriding
public void onConfigurationChanged(Configuration newConfig)
and in your activity declaration in manifest file add the following line
android:configChanges="orientation|screenSize|keyboardHidden"
2). As Aerilys said in the above answer, you can use singleton class to store data. Before displaying your loading screen check if you single ton object has data or not. If yes then skip displaying your loading screen
In my app, I want a media file to play, and to keep playing if the user rotates the screen (destroying the Activity), but I want it to stop playing if the user moves to a different Activity or another Activity appears over this one, they press the back button, whatever.
I believe there's an API for this in Honeycomb, but I need something that will work in Android 2.2.
I'd rather not use onConfigurationChanged to handle all the configuration changes myself--that sounds like a lot of work and potential bugs--and the issue with onRetainNonConfigurationInstance() is that it doesn't run until after onStop fires--but onPause would be the logical place to pause the media, if appropriate.
Is there a way, in onPause, to tell if the Activity is being paused for a configuration change versus another reason?
Your solution has at least one potential problem.
According to Android documentation:
Some device configurations can change during runtime (such as screen orientation, keyboard availability, and language). When such a change occurs, Android restarts the running Activity (onDestroy() is called, followed by onCreate()).
Testing for a change in rotation only handles one case. Since this is likely to be the most common cause of configuration change, it's an OK solution. But what if you could handle all cases, existing or added in some future version of Android, without the overhead of handling configuration yourself?
Updated to use onRetainNonConfigurationInstance. Android docs have this to say about it:
Called by the system, as part of destroying an activity due to a configuration change, when it is known that a new instance will immediately be created for the new configuration.
Also changed chain to super.onDestroy() to happen after stopping media, but not entirely sure about that. Guess it depends on what stopping media means, and what effect destroying may have on stopping media.
private Boolean mConfigurationChange = false;
public Object onRetainNonConfigurationInstance() {
mConfigurationChange = true;
return null;
}
public void onDestroy() {
if (!mConfigurationChange) {
// Code to stop media file goes here.
}
super.onDestroy();
}
Prior to Honeycomb, onRetainNonConfigurationInstance() as mentioned in the accepted answer is the best way to do this.
Starting with Honeycomb, that's deprecated, but there is a much simpler way: call isChangingConfigurations() instead, during any of your onPause()/onStop()/onDestroy().
Finally, if using the support library FragmentActivity (
android.support.v4.app.FragmentActivity) on pre-Honeycomb, onRetainNonConfigurationInstance() is declared final, but you can override onRetainCustomNonConfigurationInstance() instead for the same effect.
I finally figured out the answer to my own question!
public class MyActivity extends Activity {
Display display;
int originalRotation;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
originalRotation = display.getRotation();
}
public boolean endingDueToConfigurationChanging() {
int newRotation = display.getRotation();
return (newRotation != originalRotation);
}
}
Display.getRotation() is designed to return a constant indicating whether the device is 0, 90, 180 or 270 degrees off its natural rotation. However, when the device is rotated, this value is updated before the activity ends--it's actually updated as early as in onPause()! So if you compare the value in onPause() (the new value) with the one in onCreate() (the original value) then you know the Activity it shutting down due to a screen rotation or not.
You can try listening for the ACTION_CONFIGURATION_CHANGED intent and setting an internal flag indicating the pause is from a configuration change.
I'm not sure if you will receive the intent in time or that you will be guaranteed it will arrive in time as it is probably asynchronous. Might be worth a try though.
You can also try using View#onConfigurationChanged in case its applicable in your case(i.e - you have a view for your media player and have a reference to it in the view).
I have a Gallerywiew.
I'm using lazyload to download images but when I rotate device it reloads all images and does not use the cache.
If I do android:configChanges="keyboardHidden|orientation" the current images are in size of latest orientation.
To get the images to show full size I do:
Display display = ((WindowManager)getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
int width = display.getWidth();
int height = display.getHeight();
Overriding onConfigurationChanged() is discouraged because there's so much work you have to do to get it right.
What you want to do is implement onRetainNonConfigurationInstance() in your activity. This is called just before your activity is killed when the system knows it will be restarting it in a moment (e.g. for screen rotation).
Your implementation of onRetainNonConfigurationInstance() may return any object it likes ('this' is a good choice, or in your case, your cache). This object will be held and made available to the next invocation of your activity.
In your onCreate() method, call getLastNonConfigurationInstance() to retrieve the object the system is saving for you. If this function returns null, proceed as you would normally. If it returns non-null, then that will be the object you previously passed back from onRetainNonConfigurationInstance() and you can extract any information you want from it. This generally means that you don't need anything from the savedInstanceState bundle or from saved preferences.
I believe even open sockets, running threads, and other objects can be preserved across configuration changes this way.
Adding on: If you do pass this through onRetaineNonConfigurationInstance(), don't hang on to it. You'll keep huge amounts of resources from being freed. Extract the information you need and then release it.
You need to override your onConfigurationChanged event.
In my app I was playing music and when you flipped the phone it would stop and then I found out that if I overrode the config change I could just tell it to start my timer again and nothing was lost. I know this inst exactly what you wanted but it may help get you going in the right direction.
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
startTimer();
}
When you rotate the screen, you go through the activity lifecycle. The activity goes through onDestroy() and its onCreate() method gets called. You must redo any display calculations and view changes in onCreate and use the savedInstanceState to persist app data.
I hope this is what you were asking