Android App Losing State When Switching Tabs And Orientation - android

The problem: I have a tabbed android app and I'm losing the content in TabOne whenever I follow these (admittedly strange) steps:
Change to another tab.
Switch orientation to landscape.
Switch orientation back to portrait.
Change back to TabOne.
Android App Description: I have a pretty bare-bones android app with three tabs that were built using google's TabLayout tutorial, we'll call them TabOne, TabTwo, and TabThree. Only TabOne has any content: a simple EditText view and Button that lets you add text to the body of TabOne. This is rigged up using a custom ArrayAdapter, which may have something to do with the strange behavior.
Note that this does not occur if I change orientation while remaining on TabOne. This is because I have implemented OnSaveInstanceState() and OnRestoreInstanceState() to save my list of data in my TabOneActivity class.

I had the same problem - the solution I found was to create a 'Dummy' tab and activity for the first tab in the TabLayout onCreate, then in onResume of the Tab Layout Activity, hide the 'Dummy' tab and select the 2nd tab programmatically. Not nice, but works as saves state of 2nd tab (i.e. 1st visible tab).
#Override
protected void onResume() {
super.onResume();
if (getTabHost() != null && getTabHost().getTabWidget()!= null) {
getTabHost().getTabWidget().getChildAt(0).setVisibility(View.GONE);
if (getTabHost().getCurrentTab() == 0) {
getTabHost().setCurrentTab(1);
}
}
}

You also need to restore your activity state in onCreate, as well as in OnRestoreInstanceState.
I should point out though that this technique is only for transient data, not for long term data storage. For that you should be saving the data to a database or to SharedPreferences in onPause, and then retrieving the data in onResume.

Related

Android: Recreate android fragment when language change in settings

I have one activity and using fragments, Using same activity all the time and replacing the fragments. I have a customkeyboard in the first fragment.It opens when i click on the edit text.
The problem is when I am in the first fragment and got to settings and changing the language all other views like bunch of textviews and editexts are refreshing to language selected but here custom keyboard is not opening when i click on the edittext.
I tried hiding it on Onresume() if the view instance is already there but nothing is working
Now i want to restart the activity or fragment when i go to setting and change the language. so that the app can start fresh.
#Override
protected void onResume() {
String str = Locale.getDefault().toString();
if(str.equals("de_DE")){
Intent Intent = getIntent ();
finish ();
startActivity ( Intent );
}
super.onResume();
}
Doing the above code in my activity but it is not working
Tried detaching and attaching the fragment also not working.
You can try:
Replacing fragments with FragmentManager (i wouldn't recommend this(it is heavy task resource wise))
Implementing interface for fragments and then you can manually change content for desired language

Fragment refreshing on backpress

I have an activity MainActivity there are three fragments associated with this activity.
Now one of my fragment Timeline has a listview. Which I populate from a Database in the backend. I use an AsyncTask to fetch values from the DB and process them to the List. I trigger this AsyncTask in the onCreate of the Fragment Timeline.
Now from Timeline on click of any list item I navigate to a different Activity called as DetailActivity
The problem is whenever I press back from the DetailActivity the onCreate of my MainActivity is called and my list refreshes again - the whole DB operation is called again and my list does not retain its state.
I am calculating the visible items of my List before I navigate away from the Fragment but I am forced to use static values for these variables so that I retain the position. How to avoid this?
Below are the snippets of my onPause and onResume as laid down in the fragment Timeline
static int index;
static int top;
#Override
public void onPause(){
System.out.println("onPause");
index = lv.getFirstVisiblePosition();
View v = lv.getChildAt(0);
top = (v == null) ? 0 : v.getTop();
super.onPause();
uiHelper.onPause();
}
#Override
public void onResume(){
super.onResume();
//dbHelper.open();
System.out.println("onResumr");
lv.setSelectionFromTop(index, top);
ActionBar actionBar = getActivity().getActionBar();
actionBar.setTitle("Timeline");
uiHelper.onResume();
AppEventsLogger.activateApp(getActivity());
updateUI();
}
This also forces my AsyncTask to run again and again, which is an overhead.
Edit:
The root of this problem - After struggling for so many days I borrowed a friends phone to test and all was sorted on this new phone. I found out that I had turned on the Do not keep Activities option in my Developer Settings. The Dumb me!!
This is, unfortunately, the default behavior of the Fragment class. A Fragment is destroyed whenever the containing Activity is paused, and recreated whenever the containing Activity is resumed. If you use an Activity instead of a Fragment for the list, you would not experience the same behavior. With an Activity:
AsyncTasks and/or web services would not be called again.
The list would show the previously scrolled position.
If you want the same behavior with a Fragment, you need to override the onSaveInstanceState() method. And while interesting, it is not a small amount of work.
EDIT:
Make sure the Do not keep Activities option is unselected in your phone's Developer Settings. This, though, does not change the essential behavior of the Fragment class that I have outlined above.
You can call setRetainInstance(true) on your fragment. The lifecycle will be slightly different though.
A nice view of a fragment's lifecycle is available here
http://corner.squareup.com/2014/10/advocating-against-android-fragments.html

Android actionbar refresh when rotating and multi-pane

I have an activity the same as the following image:
FragmentA is a listview and has a SearchWidget as menu-item (which is not displayed on old devices, only API11 and above).
FragmentB is a detail view and has several menu-items.
When ActivityA runs on a tablet, the menu-items of FragmentA + FragmentB are visible in the actionbar. This is correct and works perfect.
Now on a Nexus 7 I want a mix of those:
In portrait only use the handset layout
When I rotate the device, the tablet layout is loaded
The only thing which I can't seem to get working is the actionbar. When I rotate the device from landscape mode (tablet view) back to portrait (handset view), still the actionbar shows the menu-items of FragmentA + FragmentB.
I've tried calling the invalidateOptionsMenu() from onResume() in both ActivityA as FragmentA, but without luck.
Does anyone has an idea?
I think this is due activity re-creation process.
When screen is rotated your activity is destroyed (by default).
But before it is destroyed it saves state including states of all currently active fragments.
Later, when activity is creating after orientation change it restores saved state (with both fragments). As details fragment restores it appends menu items.
You can check this by adding log statements or using debugger in onCreateView of DetailsFragment.
If it's your case then you have next solutions:
Suppress saving state (must be avoided if DetailsFragment should keep track of last displayed item or something similar) by removing this fragment before activity save its state.
Suppress any initialization of DetailsFragment if it will not be displayed. Activity should give answer about visibility.
Your variant? I think two approaches above isn't good enough...
Sorry if my answer didn't help you at all
This works for me.
In Activity A try find Fragment B and set setHasOptionsMenu(mTwoPane)
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_document_list);
Fragment fragment = getFragmentManager().findFragmentByTag(TAG_FRAGMENT_DETAIL);
if (getResources().getBoolean(R.bool.has_detail)){
mTwoPane = true;
....
}
if (fragment != null) {
fragment.setHasOptionsMenu(mTwoPane);
invalidateOptionsMenu();
}
}

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);
}

Android tabs - avoid recreating activities on tab clicks

I've put a little app together that has three tabs to show three different web pages. It does work however I am bit worried I haven't got enough control over how this whole thing works. When I click a tab, I get a web page loaded (see code sample below), now when I click another tab another page loads in another view. When I go back to the first tab, the whole thing get initilized again and the page loads. Is there a way how I can control this and keep the underneeth tab's activity in its current state as long as I want (and say only "refresh" the page when it changes).
do I need to handle onPause()/onResume() methods for that or instead implement my tabs as views of a single activity (is this possible at all?)? How do I store the state of my activity to avoid re-initializing it every time?
this how activities are hooked to tabs:
intent = new Intent().setClass(this, tab_schedule.class);
spec = tabHost.newTabSpec("Schedule").setIndicator("Schedule",
res.getDrawable(R.drawable.tab_icon_schedule)).setContent(
intent);
tabHost.addTab(spec);
the tab_schedule.class does a simple web page load:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tab_people);
try {
WebView currentView = (WebView) findViewById(R.id.tab_people_WebView);
currentView.getSettings().setJavaScriptEnabled(true);
currentView.loadUrl("http://pda.lenta.ru");
} catch (Exception e) {
Log.v("webClientInit", e.getMessage());
}
}
If you don't want to create a new activity for each tab, you can use a TabWidget with a FrameLayout to toggle between views.
As to switching activities, see this question for a way to not recreate the activity each time.
Regardless, you should always implement onPause and onResume to restore the state of your app. You might want to read up on the Activity Lifecycle, but basically you cannot prevent your activity from being killed if goes into the background. Thus, you should store your state in onPause. The link above has some info on how to do so as well.
To bring the previous activity to the top of the stack use intent.addFlag(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);

Categories

Resources