Persisting a context menu after screen rotation - android

I have an activity that on it's onCreate method it does:
registerForContextMenu(theView);
and in onCreateContextMenu:
super.onCreateContextMenu(menu, v, menuInfo);
menu.add(blablabla);
This works great, but the problem is that the context menu disappears when the screen rotates.
How to fix this?
Thanks for reading!

Here's the solution:
The contextMenu disappeared because by default when rotating android calls destroy() and then onCreate() but :
If you don't want Android to go through the normal activity destroy-and-recreate process; instead, you want to handle recreating the views yourself, you can use the android:configChanges attributes on the element in AndroidManifest.xml.
<activity
android:name=".SmsPopupActivity"
android:theme="#android:style/Theme.Dialog"
android:launchMode="singleTask"
android:configChanges="orientation|keyboardHidden"
android:taskAffinity="net.everythingandroid.smspopup.popup">
</activity>
This way my contextMenu is not closed when my phone rotates, because onCreate() method is not called.
See also:
Developing Orientation-Aware Android Applications
activity-restart-on-rotation-android

According to the Android developers blog:
The Activity class has a special method called onRetainNonConfigurationInstance(). This
method can be used to pass an
arbitrary object your future self and
Android is smart enough to call this
method only when needed. [...]
The implementation can be summarized
like so:
#Override public Object
onRetainNonConfigurationInstance() {
final LoadedPhoto[] list = new LoadedPhoto[numberOfPhotos];
keepPhotos(list);
return list; }
In the new activity, in onCreate(),
all you have to do to get your object
back is to call
getLastNonConfigurationInstance(). In
Photostream, this method is invoked
and if the returned value is not null,
the grid is loaded with the list of
photos from the previous activity:
http://android-developers.blogspot.com/2009/02/faster-screen-orientation-change.html?utm_source=eddie

I may be wrong, but from what I know you cant persist it, however (this is the part where i may be wrong in) you could open the menu dynamically after you rotate. Giving the illusion of persistence.

Related

Testing Menu with Robolectric

I am currently learning Robolectric to test for Android and I am having trouble obtaining my application's menu.
Right now, Robolectric's getOptionsMenu() is returning null. The code itself works fine but the test always returns null for the options menu.
My code is the following
#Test
public void onCreateShouldInflateTheMenu() {
Intent intent = new Intent();
intent.setData(Uri.EMPTY);
DetailActivity dActivity = Robolectric.buildActivity(DetailActivity.class, intent).create().get();
Menu menu = Shadows.shadowOf(dActivity).getOptionsMenu(); // menu is null
MenuItem item = menu.findItem(R.id.action_settings); // I get a nullPointer exception here
assertEquals(menu.findItem(R.id.action_settings).getTitle().toString(), "Settings");
}
Does anyone know why Robolectric is returning null? Did I miss any dependencies?
The onCreateOptionsMenu will be called after oncreate so to make sure that you can see your menu try
Robolectric.buildActivity(DetailActivity.class, intent).create().resume().get();
or you can make sure the activity is visible
Robolectric.buildActivity(DetailActivity.class, intent).create().visible().get();
From docs
What’s This visible() Nonsense?
Turns out that in a real Android app, the view hierarchy of an
Activity is not attached to the Window until sometime after onCreate()
is called. Until this happens, the Activity’s views do not report as
visible. This means you can’t click on them (amongst other unexpected
behavior). The Activity’s hierarchy is attached to the Window on a
device or emulator after onPostResume() on the Activity. Rather than
make assumptions about when the visibility should be updated,
Robolectric puts the power in the developer’s hands when writing
tests.
So when do you call it? Whenever you’re interacting with the views
inside the Activity. Methods like Robolectric.clickOn() require that
the view is visible and properly attached in order to function. You
should call visible() after create().
Android: When is onCreateOptionsMenu called during Activity lifecycle?

Maintaining Progress Bar Visibility with Orientation Change

I have a progress bar (swirly waiting style) defined in xml as:
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="#android:style/Widget.Holo.ProgressBar.Large"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:id="#+id/progress"
/>
I hide it's visibility in the activity's onCreate method using,
progressBar.setVisibility(View.GONE);
and start it on a button's onClick event using
progressBar.setVisibility(View.VISIBLE);
Now if I change the screen oreintation the progress bar disappears. I understand that the activity is destroyed and recreated on an orientation change, and the state of the activity is recreated in the new orientation from the saved Bundle savedInstanceState. So am I right in thinking that the default Bundle saved by android does not include any changes made to to a ProgressBar View object?
If this is the case, is it correct to say that the only way to reinstate the correct visibility of the ProgressBar after an orientation change is to save a flag (e.g. boolean pbState = false/true) by overriding the method onSaveInstanceState and inspecting this flag in onRestoreInstanceState and setting the visibility accordingly? Or, am I missing something really obvious about saving the state of view objects.
Thanks
UPDATE:
Both the solutions provided below work. I decided to opt for putting android:configChanges="orientation|screenSize" in the manifest xml file. However, the documentation states that this method should only be used as a last resort. My activity is fairly simple, and so the manifest xml method reduces the amount of code required in the main activity, i.e., no onRestoreInstanceState method. I presume if you're activity is more complex, you'll probably want to explicitly define any state changes using the latter method.
So am I right in thinking that the default Bundle saved by android
does not include any changes made to to a ProgressBar View object?
You are right. Android will not save the state of progressBar, or any other widget for that matter.
[Is] it correct to say that the only way to reinstate the correct
visibility of the ProgressBar after an orientation change is to save a
flag (e.g. boolean pbState = false/true) by overriding the method
onSaveInstanceState and inspecting this flag in onRestoreInstanceState
and setting the visibility accordingly?
Absolutely. About onRestoreInstanceState(Bundle): You can do without overriding this method. To confirm orientation change, check for savedInstanceState ==> Bundle passed to onCreate(Bundle) against null. If an orientation change has occurred, savedInstanceState will not be null. On start of an activity, savedInstanceState will be null. Following code (which is basically what you proposed) should do the job:
Declare a global boolean variable:
boolean progressBarIsShowing;
In your onCreate(Bundle):
// savedInstanceState != null ===>>> possible orientation change
if (savedInstanceState != null && savedInstanceState.contains("progressbarIsShowing")) {
// If `progressBarIsShowing` was stored in bundle, `progressBar` was showing
progressBar.setVisibility(View.VISIBLE);
} else {
// Either the activity was just created (not recreated), or `progressBar` wasn't showing
progressBar.setVisibility(View.GONE);
}
Whenever you show progressBar, set progressBarIsShowing to true. And toggle it when you dismiss progressBar.
Override onSaveInstanceState(Bundle):
if (progressBarIsShowing) {
outState.putBoolean("progressBarIsShowing", progressBarIsShowing);
}
Caution: Check for when user browses away from your activity(via home button press etc). You might get a BadTokenException if progressBar is showing when the user does so.
You use the following line inside your activity tag in manifest.
<activity android:name="your activity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"/>
In the above android:configChanges="orientation" will maintain the state of your application while configuration change.
Using android:configChanges is bad practice, because it might get you in much worse trouble.
Also saving a variable at onSaveInstanceState didn't work for me, since you won't get updates while the Activity is destroyed.
I ended up using a ResultReceiver inside a Fragment that doesn't get destroyed by using setRetainInstance(true).
A good article concerning this problem can be found here:
https://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
Also see my answer here: https://stackoverflow.com/a/54334864/6747171

Android : Save application state on screen orientation change

I have seen the following links before posting this question
http://www.devx.com/wireless/Article/40792/1954
Saving Android Activity state using Save Instance State
http://www.gitshah.com/2011/03/how-to-handle-screen-orientation_28.html
How to save state during orientation change in Android if the state is made of my classes?
I am not getting how should i override the following function :
#Override
public Object onRetainNonConfigurationInstance() {
return someExpensiveObject;
}
In my application i have layout with one editext visible and other editext get visible when the data of first editext validates to true.I have set the visbility of all other editextes and textviews to false and make them visible after validating.
So in my activity if the screen orientation is changed then all the items having android:visibility="false" get invisible.
I have also came to know that when our activities screen orientation changes it calls onStop() followed by onDestroy() and then again starts a fresh activity by calling onCreate()
This is the cause .. But i am not getting how to resolve it ..
Here You can see the screenshots of my application :
in this image all fields are loaded
and in another image when the screen orientation is changed to landscape they are all gone
Any link to tutorial or piece of code will be highly appreciable.
And also my application crashes when a progress dialog is shown up and i try to change screen orientation.How to handle this ??
Thanks
Well if you have the same layout for both screens then there is no need to do so just add below line in your manifest in Activity node
android:configChanges="keyboardHidden|orientation"
for Android 3.2 (API level 13) and newer:
android:configChanges="keyboardHidden|orientation|screenSize"
because the "screen size" also changes when the device switches between portrait and landscape orientation.
From documentation here: http://developer.android.com/guide/topics/manifest/activity-element.html
There is another possibility using which you can keep the state as it is even on Orientation change using the onConfigurationChanged(Configuration newConfig).
Called by the system when the device configuration changes while your activity is running. Note that this will only be called if you have selected configurations you would like to handle with the configChanges attribute in your manifest. If any configuration change occurs that is not selected to be reported by that attribute, then instead of reporting it the system will stop and restart the activity (to have it launched with the new configuration).
At the time that this function has been called, your Resources object will have been updated to return resource values matching the new configuration.
There are 2 ways of doing this, the first one is in the AndroidManifest.xml file. You can add this to your activity's tag. This documentation will give you an in depth explanation, but put simply it uses these values and tells the activity not to restart when one of these values changes.
android:configChanges="keyboardHidden|orientation|screenSize|screenLayout"
And the second one is: overriding onSaveInstanceState and onRestoreInstanceState. This method requires some more effort, but arguably is better. onSaveInstanceState saves the values set (manually by the developer) from the activity before it's killed, and onRestoreInstanceState restores that information after onStart() Refer to the official documentation for a more in depth look. You don't have to implement onRestoreInstanceState, but that would involve sticking that code in onCreate().
In my sample code below, I am saving 2 int values, the current position of the spinner as well as a radio button.
#Override
public void onSaveInstanceState(#NonNull Bundle savedInstanceState) {
spinPosition = options.getSelectedItemPosition();
savedInstanceState.putInt(Constants.KEY, spinPosition);
savedInstanceState.putInt(Constants.KEY_RADIO, radioPosition);
super.onSaveInstanceState(savedInstanceState);
}
// And we restore those values with `getInt`, then we can pass those stored values into the spinner and radio button group, for example, to select the same values that we saved earlier.
#Override
public void onRestoreInstanceState(#NotNull Bundle savedInstanceState) {
spinPosition = savedInstanceState.getInt(Constants.KEY);
radioPosition = savedInstanceState.getInt(Constants.KEY_RADIO);
options.setSelection(spinPosition, true);
type.check(radioPosition);
super.onRestoreInstanceState(savedInstanceState);
}

savedInstanceState is always null

This is my savedInstaceState code:
#Override
public void onSaveInstanceState(Bundle savedInstanceState)
{
savedInstanceState.putStringArrayList("todo_arraylist", Altodo);
Log.v("bundle", "Saved");
super.onSaveInstanceState(savedInstanceState);
}
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (savedInstanceState != null)
{
Altodo = savedInstanceState.getStringArrayList("todo_arraylist");
Log.v("bundle", "Restored");
}
else
{
Log.v("bundle", "null");
}
setContentView(R.layout.main);
}
The logs always show the "bundle save" tag.
But in onCreate method, SavedInstanceState is always null.
I observed the exact same symptoms (reported as issue 133394) in a project with two Activities A and B that extend ActionBarActivity. Activity A is the main activity, and I always receive null for savedInstanceState in onCreate of its list fragment when returning from a detail view activity B. After many hours, this problem exposed itself to me as a navigation issue in disguise.
The following may be relevant to my setup and come from other answers on this page:
Given this answer, I made sure that fragment and activity each have unique IDs set.
There is no override of onSaveInstanceState without super call.
Activity A is specified as acitivy B's parent in AndroidManifest.xml, using both the android:parentActivityName attribute and the corresponding meta-data tag for earlier versions of Android (see "Providing Up Navigation").
Already without any corresponding creation code such as getActionBar() .setHomeButtonEnabled(true), activity B has a functioning back button (<) in its action bar. When this button is tapped, activity A reappears but with (a) all previous instance state lost, (b) onCreate always called, and (c) savedInstanceState always null.
Interestingly, when I tap the back button provided at the bottom edge of the emulator display (an open triangle that points to the left), activity A reappears just as it was left (i.e. its instance state fully retained) without invoking onCreate. So maybe something is wrong with navigation?
After more reading, I implemented my own navigation instructions to run in response to a tap on the back-button in activity B:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home)
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
Nothing related to restoring instance state of activity A changed. NavUtils also provide a method getParentActivityIntent(Activity) and navigateUpTo(Activity, Intent) that allow us to modify the navigation intent to explicitly instruct that activity A is not started fresh (and thus without saved instance state provided) by setting the FLAG_ACTIVITY_CLEAR_TOP flag:
If set, and the activity being launched is already running in the
current task, then instead of launching a new instance of that
activity, all of the other activities on top of it will be closed and
this Intent will be delivered to the (now on top) old activity as a
new Intent.
In my hands, this solves problem of lost instance state and could look like:
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId()== android.R.id.home) {
Intent intent = NavUtils.getParentActivityIntent(this);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(this, intent);
return true;
}
return super.onOptionsItemSelected(item);
}
Note that this may not be the complete solution in other cases where a user can switch directly to activity B from within a different task (see here). Also, a possibly identical solution in behavior that does not make use of NavUtils is to simply call finish():
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId()== android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
Both solutions work in my hands. I am only speculating that the original issue is a slightly incorrect default implementation of the back-button, and it may be related to that implementation invoking some kind of navigateUp that misses FLAG_ACTIVITY_CLEAR_TOP.
Did you check if you have an Id set for that view ( if a view it is/has...). onSaveInstanceState() is not called otherwise.
Check this link.
The state saved in this manner is not persisted. If the whole application is killed as you are doing during debugging, the bundle will always be null in onCreate.
This IMO is yet another example of awful Android documentation. It's also why most apps in the marketplace don't implement saving state properly (at all).
in Manifest add this line for activities
android:launchMode="singleTop"
for example:
<activity
android:name=".ActivityUniversity"
android:label="#string/university"
android:launchMode="singleTop"
android:parentActivityName="com.alkhorazmiy.dtm.ActivityChart">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.alkhorazmiy.dtm.ActivityChart" />
</activity>
How do you test it?
Imo the best way to test it is using the "Don't keep activities"-flag in Settings > Developer Options. If you don't have Developer Options in Settings, see Enabling On-device Developer Options.
Open your activity
Long-press home
Go to another application
Long-press home
Go back to your application
Shouldn't super.onSaveInstanceState(savedInstanceState); be the first line in your override?
Edit: War_Hero points out in the comments that the documentation on that topic indicates that no, it shouldn't be the first line.
Check your activity in AndroidManifest.xml and remove android:noHistory property if is true.
<activity
// ....
android:noHistory="false" />
To debug, consider implementing onRestoreInstanceState and placing a call to Log.d in this method. Then, in the emulator, hit ctrl-F11 or whatever to rotate the phone. Your call to Log.d should be hit.
Implement a method of onRestoreInstanceState
and put below code there
Altodo = savedInstanceState.getStringArrayList("todo_arraylist");
I found that when I override onSaveInstanceState() and actually save some data in the Bundle, instance state is restored. Otherwise it's not.
Ive managed same way arround. Instead of handling savedInstanceState Bundle on the onCreateView method, ive handled it on onCreate method and setting the passed value to a globar variable then acessing this variable on the onCreateView method.
Hope it helps.
https://developer.android.com/guide/topics/manifest/activity-element#lmode
From this you can see 'Similarly, if you navigate up to an activity on the current stack, the behavior is determined by the parent activity's launch mode.' Maybe you are in the 'standard' mode.
I was able to solve it with:
#Override public boolean onSupportNavigateUp()
{
onBackPressed();
return true;
}
still had parent set in the manifest. So when you press the up navigation button, now it acts like the back button.

How to check if an activity is the last one in the activity stack for an application?

I want to know if user would return to the home screen if he exit the current activity.
I'm going to improve on the comment of #H9kDroid as the best answer here for people that have a similar question. (Original link)
You can use isTaskRoot() to know whether the activity is the root of a task.
UPDATE (Jul 2015):
Since getRunningTasks() get deprecated, from API 21 it's better to follow raukodraug answer or Ed Burnette one (I would prefer second one).
There's possibility to check current tasks and their stack using ActivityManager.
So, to determine if an activity is the last one:
request android.permission.GET_TASKS permissions in the manifest.
Use the following code:
ActivityManager mngr = (ActivityManager) getSystemService( ACTIVITY_SERVICE );
List<ActivityManager.RunningTaskInfo> taskList = mngr.getRunningTasks(10);
if(taskList.get(0).numActivities == 1 &&
taskList.get(0).topActivity.getClassName().equals(this.getClass().getName())) {
Log.i(TAG, "This is last activity in the stack");
}
Please note, that above code will be valid only if You have single task. If there's possibility that number of tasks will exist for Your application - You'll need to check other taskList elements. Read more about tasks Tasks and Back Stack
Hope this will help new beginners, Based above answers which works for me fine, i am also sharing code snippet so it will be easy to implement.
solution : i used isTaskRoot() which return true if current activity is only activity in your stack and other than i also handle case in which if i have some activity in stack go to last activity in stack instead of opening new custom one.
In your activity
#Override
public void onBackPressed() {
if(isTaskRoot()){
startActivity(new Intent(currentActivityName.this,ActivityNameYouWantToOpen.class));
// using finish() is optional, use it if you do not want to keep currentActivity in stack
finish();
}else{
super.onBackPressed();
}
}
there is an easiest solution to this, you can use isTaskRoot()
in your activity
One way to keep track of this is to include a marker when you start a new activity and check if the marker exists.
Whenever you start a new activity, insert the marker:
newIntent=new Intent(this, NextOne.class);
newIntent.putExtra(this.getPackageName()+"myself", 0);
startActivity(newIntent);
And you can then check for it like this:
boolean islast=!getIntent().hasExtra(this.getPackageName()+"myself")
While there may be a way to achieve this (see other answers) I would suggest that you shouldn't do that. Normal Android applications shouldn't need to know if the Home screen is about to display or not.
If you're trying to save data, put the data saving code in your onPause() method. If you're trying to give the user a way to change their mind about existing the application, you could intercept the key up/down for the Back key and the onBackPressed() method and present them with an "Are you sure?" prompt.
I've created a base class for all my activities, extending the AppCompatActivity, and which has a static counter:
public abstract class BasicActivity extends AppCompatActivity {
private static int activityCounter = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
++activityCounter;
...
}
#Override
public void onDestroy() {
super.onDestroy();
--activityCounter;
if(activityCounter==0) {
// Last instance code...
}
}
public boolean isLastInstance() { return (activityCounter==1); }
}
This has worked well enough, so far; and regardless of API version. It requires of course that all activities extends this base class - which they do, in my case.
Edit: I've noticed one instance when the counter goes down to zero before the app completely exits, which is when the orientation is changed and only one activity is open. When the orientation changes, the activity is closed and another is created, so onDestroyed is called for the last activity, and then onCreate is called when the same activity is created with the changed orientation. This behaviour must be accounted for; OrientationEventListener could possibly be used.
The Problem with sandrstar's solution using ActivityManager is: you need a permission to get the tasks this way.
I found a better way:
getIntent().hasCategory(Intent.CATEGORY_LAUNCHER)
The Activity on the Stack bottom should allways get this category by default while other Activities should not get it.
But even if this fails on some devices you can set it while starting your Activity:
Intent intent = new Intent(startingActivity, SomeActivityClass.class);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
activity.startActivity(intent);
Android implements an Activity stack, I suggest you read about it here. It looks like all you want to do though is retrieve the calling activity: getCallingActivity(). If the current activity is the first activity in your application and the application was launched from the home screen it should (I assume) return null.
The one thing that missed here, is the "Home key" click, when activated, you can't detect this from your activity, so it would better to control activity stack programmatically with handling "Back key" press and moving to required activity or just doing necessary steps.
In addition, you can't be sure, that starting your activity from "Recent Activity" list can be detected with presetting some extra data into intent for opening activity, as it being reused in that case.

Categories

Resources