IndexOutOfBoundsException when restoring navigation tab state - android

I'm using action bar tab navigation and when I rotate screen I get IndexOutOfBoundsException
According to logcat problem is in getSupportActionBar().setSelectedNavigationItem(
savedInstanceState.getInt(STATE_SELECTED_NAVIGATION_ITEM));
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
// Restore the previously serialized current tab position.
if (savedInstanceState.containsKey(STATE_SELECTED_NAVIGATION_ITEM)) {
getSupportActionBar().setSelectedNavigationItem(
savedInstanceState.getInt(STATE_SELECTED_NAVIGATION_ITEM));
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
// Serialize the current tab position.
outState.putInt(STATE_SELECTED_NAVIGATION_ITEM, getSupportActionBar()
.getSelectedNavigationIndex());
}
EDIT
I found the problem. I use AsyncTask to gather all info that will be showed in tabs and I add tabs in onPostExecute.
onRestoreInstanceState is called before onPostExecute so the tab count is still 0.

Try
if (savedInstanceState!= null && savedInstanceState.getInt(STATE_SELECTED_NAVIGATION_ITEM) >= 0)
instead if (savedInstanceState.containsKey(STATE_SELECTED_NAVIGATION_ITEM))

I'm not sure if onSaveInstanceState is called when the screen is rotated.
Due to this documentation "the method onSaveInstanceState(Bundle) is called before placing the activity in such a background state".
Did you check, whether the function is called when the screen is rotated?

Related

Android : Save fragment state and restore the view values

Hi iam trying to save the state of the fragment when one fragment is replaced with other fragment.
Consider in my Activity i have a fragmentA with textview-1 and textview-2. when i click on the textview-1 i am replacing fragmentA with fragmentB which has a list view.
Now when i click on the fragmentB list item iam getting the list value and updating the textview-1. same thing iam doing for textview-2 but when i return back, the textview-1 value is gone. How to save the state of the fragment A and its textview-1 value.
Tried as below
#Override
public void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
super.onSaveInstanceState(outState);
outState.putString("curChoice", strtext);
}
and in OnActivityCreated()
if (savedInstanceState != null) {
// Restore last state for checked position.
strtext = savedInstanceState.getString("curChoice", "");
//incrementdata(strtext);
}
But always the savedInstanceState is giving null.
When any Fragment is destroyed by the system, everything will just happen exactly the same as in an Activity. It means that every single member variables are also destroyed with the fragment. You will have to manually save and restore those variables through the onSaveInstanceState and onActivityCreated methods respectively.
There is no onRestoreInstanceState method inside a Fragment.
So you need to do something like as below code.
// These variable are destroyed along with Activity
private String var1,var2;
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("VAR1", var1);
outState.putString("VAR2", var2);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
var1 = savedInstanceState.getInt("VAR1");
var2 = savedInstanceState.getString("VAR2");
}
Basically every single standard widget such as EditText, TextView, Checkbox and etc. are all already internally implemented those things. You may need to enable it for some View for example you have to set android:freezeText to true for TextView to use the feature.

Page position on a ViewPager when change from portrait to landscape

I have a problem when I change from portrait to landscape orientation when programing a gallery. The page position in the method PageAdapter.instantiateItem(View container, int position) turns 0 when change the screen orientation. I want to store the current page position to keep in this page when the orientation changes.
You will need to store the currently displayed page in your saved instance state. For how to save state, please read the documentation.
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the current item
savedInstanceState.putInt("current_item", pager.getCurrentItem());
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
After an orientation change, that state can be restored and used to set your pagers position.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // Always call the superclass first
// Check whether we're recreating a previously destroyed instance
if (savedInstanceState != null) {
// Restore value of members from saved state
mCurrentItem = savedInstanceState.getInt("current_item");
}
}
Call pager.setCurrentItem(mCurrentItem); e.g. in your onStart() or onResume() method.
For how to get and set the currently selected item read the ViewPager documentation:
getCurrentItem()
setCurrentItem()

Android: Why is onTabSelected being called when tabs are created?

When the action bar has tabs, I've noticed that onTabSelected is called when the activity loads on screen. It also is being called whenever an orientation change occurs. My code queries the database depending on the tab being selected, and displays the query results to the loaded layout.
My problem is when saving tab state, and the current selected tab is 1 or higher, on restore state, since onTabSelected is called by default on the 0 tab, it will be called again when restored state tab is 1 or higher. This makes database query on tab 0 useless.
How to configure android that onTabSelected isn't called on tab creation or at least detect that this call is default and not user triggered?
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
int tabPos = tab.getPosition();
switch(tabPos) {
case 0:
// query database and display result
break;
case 1:
// a different query and display result
break;
case 2: ...
}
}
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
if(savedInstanceState.containsKey(STATE_SELECTED_TAB)) {
getActionBar().setSelectedNavigationItem(savedInstanceState.getInt(STATE_SELECTED_TAB));
}
super.onRestoreInstanceState(savedInstanceState);
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(STATE_SELECTED_TAB, getActionBar().getSelectedNavigationIndex());
super.onSaveInstanceState(outState);
}
Added complication:
When the current selected tab is 0, and the orientation changes, onTabSelected is still called twice! Once when the tabs are initially created, and 2nd time when onRestoreState restores the saved tab selected state, even though it is 0.
What I originally supposed was that onTabSelected was called twice, but I was mistaken. It was my fragment onCreateView being called twice, some errors in fragment transaction that added the same fragment twice on orientation change. onTabSelected is called once, and restore state calls onTabReselected is called too when the restored tab is also 0.
After scouring SO and google, I've found this question to have similar cause of problem.
Creating ActionBar tab also calling its selectTab functions
So after reviewing the reference docs on ActionBar from Google's Android site, addTab method is the one responsible for calling onTabSelected by default.
public abstract void addTab (ActionBar.Tab tab)
Add a tab for use in tabbed navigation mode. The tab will be added at the end of the list. If this is the first tab to be added it will become the selected tab.
Parameters
tab Tab to add
Incidentally, other overloaded methods exist that do not call onTabSelected.
public abstract void addTab (ActionBar.Tab tab, boolean setSelected)
So I used these alternative methods instead and it fixed the problem.
However, once the tabs are displayed, the first tab may appear selected even though it's not. Clicking on it will call onTabSelected and not onTabReselected.
I solved it in this way:
Call setupWithViewPager before tabLayout.addOnTabSelectedListener
tabLayout.setupWithViewPager(viewPager)
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {....}
.....
}
because when you call setupWithViewPager, this will internally call
setOnTabSelectedListener(new
ViewPagerOnTabSelectedListener(viewPager));
so you better call it before adding tabSelect Listener to tabLayout
i think you can do this (and ignore any typo please :-)) :
public class MainActivity extends FragmentActivity {
boolean isConfigChanged;
int savedTabIndex;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(savedInstanceState != null){
if(savedInstanceState.containsKey(STATE_SELECTED_TAB)) {
savedTabIndex = savedInstanceState.getInt(STATE_SELECTED_TAB);
//getActionBar().setSelectedNavigationItem(savedTabIndex); actually you do not need this
}
isConfigChanged = true;
}
// here add actionbar tabs
//...}
and in :
below code checks that if configuration changes and the user selected tab is not zero so this is default call and ignore but if isConfigChanged == true and the user selected tab is 0 you must query DB or if isConfigChanged == false you must query DB because it is first time that app is loading. a quick play may fit it to what you want.
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
if(isConfigChanged && savedTabIndex != 0){
isConfigChanged = false;
return;
}
isConfigChanged = false;
int tabPos = tab.getPosition();
switch(tabPos) {
case 0:
// query database and display result
break;
case 1:
// a different query and display result
break;
case 2: ...
}
}
remove onRestore
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(STATE_SELECTED_TAB, getActionBar().getSelectedNavigationIndex());
super.onSaveInstanceState(outState);
}

Android ViewPager FragmentAdapter

In my application i have a FragmentPager. Now each Fragment has a next button with which i navigate to the next fragment. Via the next button i know that the user is navigation away from the view. But how do i know if the user has clicked on the tabs. Will the
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(KEY_CONTENT, mContent);
}
function be called when a user presses a different tab ? Can i save the state and restore it on the
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if ((savedInstanceState != null)
&& savedInstanceState.containsKey(KEY_CONTENT)) {
mContent = savedInstanceState.getString(KEY_CONTENT);
}
}
will this work ? What other way can i know that the user has clicked on the different tab ?
Kind Regards
First of all, are you using ActionBarSherlock for you actionbar/tabs? I recommend that you do since you'll get a cross-version way of working with the actionbar.
In any case, you should add a listener to each tab before adding it to the actionbar. With the listener implemented you know when a tab has been selected, reselected and unselected
I'm not sure about when the onSaveInstanceState is called (try it using the debugger!), but with the listener implemented you'll get a fool-proof way of knowing what goes on with your tabs.

Restoring displayed fragment on activity rotation

I have a fragment with one main fragment displayed on it. When I rotate the screen, the activity is beeing recreated. What would be the best way to store the info about currently displayed fragment for screen state restore purposes?
You can use the method onSaveInstanceState to store values in a bundle:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(MY_FIRST_KEY,valueToSave);
}
then you can restore the values in the method onRestoreInstanceState
#Override
protected void onRestoreInstanceState(Bundle inState) {
super.onRestoreInstanceState(inState);
valueToSave = inState.getString(MY_FIRST_KEY);
}
You can also set an xml attribute on TextViews and EditText called "freezeText" which saves any text that was set.

Categories

Resources