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.
Related
I have a MainActivity that extends SherlockActivityFragment. This activity holds some fragments and a menu. Each menu item leads to new SherlockActivities. One particular SherlockActivity when it is loaded contains a carousel, that moves extremely slow.
However, once the orientation changes this carousel performs perfectly. I know that the Activity is reloaded on orientation change. But I have no idea what could start this to begin with, or even where to start looking.
Has anyone come across any similar issues? What did you do to profile the issue and what was your fix?
If you can think of relevant parts of the code (eg onCreate etc) please ask and I'll post it. Each activity has about 150 - 200 LoC)
Edit:
Here is the intent that I am using to start the SherlockActivity in question:
Intent rankIntent = new Intent(context, RankActivity.class);
rankIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
context.startActivity(rankIntent);
break;
I've read that on orientation change onDestory() is called followed by onCreate. This makes me think that maybe some resource is freed that wasn't initially available. But the flag in the intent is intended to clear the backstack and any resources with it.
I've also tested with using the finish() method just before loading the new activity with no improvements.
This seems like a far reach but I recently had a problem I'd say could be similar.
I learnt that Android doesn't handle scaling images particularly well - could it be that the images are scaled before you change the orientation and not scaled once you do?
Answering the 2nd part of your question: since my app doesn't perform any resource-needy logic operations and is based on its UI, what I did was comment out some of its elements one at a time to see if it helps; that's how I found out an ImageView that was scaled up in my case.
A strange behavior I'm tearing my eyes on since early this afternoon, I'm givin up understanding but perhaps someone has an idea (yeah, I'm a beginner, some PROBABLY has an idea ^^).
Situation :
MainActivity.java (first one to be called, only one of interest here)
public ExpandableListView listClubs;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listClubs = (ExpandableListView) findViewById(R.id.expLstMainClubs);
AppGlobal.CreateDistricts() ;
ELVAdapterDistrictsClubs adapter = new ELVAdapterDistrictsClubs(this);
listClubs.setAdapter(adapter);
}
it's the only code in the class. Basically, I'm filling an ExpandableList with an adapter that relies on what's created in the CreateDistricts() method (creates business objects, Districts containing Clubs containing Members, Events, etc.).
Basically (again ^^), everything runs fine, on first start the ExpList works as expected, as well as the rest of the app. If I hit the back button from the MainActivity, however, and rerun the app, a "strange" (to me at least) behavior occurs :
The ExpList is loaded twice in a row - first one with the data, works fine, and just below a duplicate (with the same 2 Groups, i.e. here the only two Districts in my sample data), which doesn't work at all (if you try to expand it, it crashes the app).
Frankly, I'm lost - I've tried some things on the various on[Pause/Stop/Destroy/Start/Resume] etc. to no avail (therefore, there is yet nothing done on this side, no override, as it seems not to bring anything good).
As the onCreate will after the onDestroy load the bundle, it should load the ExpList as it were, and in this case there might be a conflict between the "old" groups/children and the actual ones (currently, data is simulated, but will afterward come from a remote data source), and I haven't decided yet what the best "save" behavior is, i.e. if I should find a way to obliterate the ExpList onDestroy in order to be sure it's 100% recreated anew (and how does one do that ?) and start from scratch, or if I could use the ExpList as is, while updating it's content, in order not to lose the previous selection. It may be irrelevant to complicate matters in order to do that, the first list being not so big and quite quickly used to reach the second activity...
Anyway, I suspect it comes from around that part, the bundle load after destroy and rerunning, does it reload the ExpList as is and then I add things instead of first deleting ? What I found confusing is that the next activity (reached simply by clicking one of the items of the ExpList) displays a similar ExpList (filled with Months and Events per Month for the selected Club), and doesn't present the same strange behavior after a destroy... and both have an almost identical ExpList declaration in their original xml layout so... well, perhaps tomorrow morning I'll see the light, but if anyone has an idea, feel free :D
The more I write, the more I think I should first clear the ExpList but... I don't see how exactly. I've tried
listClubs.removeViewsInLayout(0, listClubs.getCount()) ;
but it just doesn't seem to do anything.
Thanks in advance
Nothing related to Android behavior - I was recreating everytime the whole set of BO behind if the base ArrayList of Districts was not null... instead of simply getting out of the method, but it raises another question - how was the state of this ArrayList, which is declared in a subclass of Application serving as Application in my manifest ? I'll have to make some tests about that...
I am developing an app with capturing the image as one of the feature. In my home screen i have two spinner. After selecting spinner values user can go capturing the picture by clicking take picture button. Up to this working fine. But the problem resides in Camera App Orientation.
Take Picture button launches the camera.When image get captured it saved(absolutely fine) and came back to the parent activity. But the problems is it refreshes the activity. while coming back i can see following wired things
1.)Some times it shows landscape screen(1second) and back to portait which refreshes the activity and results in resetting the Spinner values.
2.) Some times it just resets the Spinner values.
It's really annoying. I haven't got any clue to get rid this problem. I hope some of you guys will solve this.
Much Appreciated.
It's good to be prepared for anything when you're launching an activity in another app (Camera). The activity could return bad data, the screen orientation could be changed, or it could be a very long time before the user returns to your app. From your description, it sounds as though the orientation is changing every time the Camera app is launched.
Android has built-in state management methods to handle this type of scenario. You can override the onSaveInstanceState() method to store your activity's state (e.g., spinner values), and then restore that state in onCreate(). (Example here.) This will handle the case in which you're launching the Camera app, or your user presses the Home button and then returns to the app, etc.
EDIT:
onRestoreInstanceState() is only called if your activity is killed due to memory pressure and then recreated later. It's much more reliable to use the Bundle passed to onCreate() (which is called again after device rotation) instead. That Bundle will contain everything that was stored in onSaveInstanceState().
If you need to postpone your work until onResume(), you'll want to use onCreate() to populate some member variables in your Activity class, and then make use of those variables in onResume().
I have an Activity that should only get created once. That is, onCreate can only be called once. If it's called again, I want the Activity to do nothing.
Is it advisable to do the following?
protected void onCreate(Bundle savedInstanceState) {
this.setTheme(android.R.style.Theme_Translucent_NoTitleBar_Fullscreen);
super.onCreate(savedInstanceState);
if(onCreateWasCalledAlreadyBoolean) {
setResult(RESULT_OK);
finish();
return;
}
//Do other stuff here
}
I assume you understand how the activity life cycle works. I mean, you are not trying to avoid something that does not apply here (thinking that onCreate may be called multiple times whenever it just onRestarts etc.).
Technically, it's perfectly fine.
However, you should be worrying more about why you need to call your activity ("A") again if it shouldn't be created at all, if that's what you're thinking.
If you've caught yourself checking if your activity A was already "called" (?), this could mean the previous activity ("B") has a mistake in the logic flow of the app, and that B instead should be checking if it must in fact start that activity A. I mean, if you need to decide if you must call an activity, check before starting it.
I don't think that's applicable if you're restarting the activity (e.g.: go Home, then navigate back), but then again you should be restarting it from where you left (B for what I can tell). You won't be navigating back to A. And you didn't give much detail, so I'd guess this is some kind of splash screen, like evilmage93 said.
If that's indeed some kind of splash screen, I would advise to show it whenever the user navigates back all the way to remove your app from the task stack (contrary to his advice). In other words, whenever the user restarts the app from its "front door".
Although that's ultimately a design decision, I prefer to see the splash screen whenever the app is being loaded ("entered") in the stack for the first time, and it should work fine if you (obviously) finish A before calling B (the splash screen is supposed to finish itself when done, even in its first run). It's a matter of consistency: the same app should behave the same way whenever the user performs the same task (start app from its "front door").
Still, I answered your question covering some general aspects because you asked in such way.
// edited:
Finally, by looking at that onCreateWasCalledAlreadyBoolean I'm afraid you may be trying to reinvent part of the activity life cycle mechanism. In this case, don't: proceed with your regular activity logic because the user expects that behavior. Generally I wouldn't advise people to break the normal loading of an activity just because it was killed and restarted by the system.
I don't see why not. Wouldn't it be simpler to not restart the activity at all though?
What are you worried about NOT being okay? Performance..Uncaught exceptions..Code clarity?
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;
}
}