I have been using a FragmentStatePagerAdapter in one of my apps for many months, and it has worked flawlessly. I recently switched to IntelliJ IDEA and updated to ADT 22 and now the Adapter causes the app to force close when the app tries to destroy tabs and create new ones.
When working correctly, the Activity will load a series of tabs. The user can swipe between them. All of this still works correctly.
The app should destroy these tabs and create new ones when the user chooses a button in the menu. Normally, this works just fine but now it has suddenly stopped working. Here is the code:
/** TabsAdapter - Make Tabs Swipe-Able, Handle Fragments */
public static class TabsAdapter extends FragmentStatePagerAdapter implements ActionBar.TabListener, ViewPager.OnPageChangeListener {
private final Context mContext;
private final ActionBar mActionBar;
private final ViewPager mViewPager;
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
static final class TabInfo {
private final Class<?> clss;
private final Bundle args;
TabInfo(Class<?> _class, Bundle _args) {
clss = _class;
args = _args;
}
} // end TabInfo
public TabsAdapter(SherlockFragmentActivity activity, ViewPager pager) {
super(activity.getSupportFragmentManager());
mContext = activity;
mActionBar = activity.getSupportActionBar();
mViewPager = pager;
mViewPager.setAdapter(this);
mViewPager.setOnPageChangeListener(this);
} // end TabsAdapter
public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args) {
TabInfo info = new TabInfo(clss, args);
tab.setTag(info);
tab.setTabListener(this);
mTabs.add(info);
mActionBar.addTab(tab);
notifyDataSetChanged();
} // end addTab
public void removeTab(ActionBar.Tab tab) {
mTabs.remove(tab.getTag());
mActionBar.removeTab(tab);
notifyDataSetChanged();
}
@Override
public int getItemPosition(Object object) {
return TabsAdapter.POSITION_NONE;
}
@Override
public int getCount() {
return mTabs.size();
} // end getCount()
@Override
public Fragment getItem(int position) {
TabInfo info = mTabs.get(position);
return Fragment.instantiate(mContext, info.clss.getName(), info.args);
} // end getItem()
@Override
public void onPageScrolled(int position, float positionOffest, int postionOffsetPixels){
} // end onPageScrolled
@Override
public void onPageSelected(int position) {
mActionBar.setSelectedNavigationItem(position);
} // end onPageSelected
@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
Object tag = tab.getTag();
for (int i = 0; i < mTabs.size(); i++) {
if (mTabs.get(i) == tag) {
mViewPager.setCurrentItem(i);
}
}
} // end onTabSelected
@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft){} // end onTabUnselected
@Override
public void onTabReselected(Tab tab, FragmentTransaction ft){} // end onTabReselected
@Override
public void onPageScrollStateChanged(int arg0) {}
} // end TabsAdapter
During onCreate(), I call a method, let's call it CreateTabs1(), and it successfully builds the tabs and the user can swipe. It is only when the pager tries to destroy the tabs and create new ones that it now fails all of the sudden, even if I call CreateTabs1() again. Again, this always worked flawlessly before.
Here is the log:
05-25 23:41:24.857: ERROR/AndroidRuntime(27290): FATAL EXCEPTION: main
java.lang.IllegalStateException: The application's PagerAdapter changed the adapter's contents without calling PagerAdapter#notifyDataSetChanged! Expected adapter item count: 11, found: 10 Pager id: com.######:id/pager Pager class: class android.support.v4.view.ViewPager Problematic adapter: class com.#####.######.#####$TabsAdapter
at android.support.v4.view.ViewPager.populate(ViewPager.java:959)
at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:548)
at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:507)
at android.support.v4.view.ViewPager.setCurrentItem(ViewPager.java:488)
at com.#####.#####.#####$TabsAdapter.onTabSelected(#####.java:215)
at com.actionbarsherlock.internal.app.ActionBarWrapper$TabWrapper.onTabSelected(ActionBarWrapper.java:356)
at com.android.internal.app.ActionBarImpl.selectTab(ActionBarImpl.java:570)
at com.android.internal.app.ActionBarImpl.removeTabAt(ActionBarImpl.java:544)
at com.android.internal.app.ActionBarImpl.removeTab(ActionBarImpl.java:520)
at com.actionbarsherlock.internal.app.ActionBarWrapper.removeTab(ActionBarWrapper.java:409)
at com.#####.#####.#####$TabsAdapter.removeTab(#####.java:181)
at com.#####.#####.#####.tabDrop(#####.java:381)
at com.#####.#####.#####.onOptionsItemSelected(#####.java:459)
at android.support.v4.app.Watson.onMenuItemSelected(Watson.java:118)
at com.actionbarsherlock.ActionBarSherlock.callbackOptionsItemSelected(ActionBarSherlock.java:603)
at com.actionbarsherlock.internal.ActionBarSherlockNative.dispatchOptionsItemSelected(ActionBarSherlockNative.java:93)
at com.actionbarsherlock.app.SherlockFragmentActivity.onMenuItemSelected(SherlockFragmentActivity.java:205)
at com.android.internal.policy.impl.PhoneWindow.onMenuItemSelected(PhoneWindow.java:980)
at com.android.internal.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:735)
at com.android.internal.view.menu.SubMenuBuilder.dispatchMenuItemSelected(SubMenuBuilder.java:81)
at com.android.internal.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:149)
at com.android.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:874)
at com.android.internal.view.menu.MenuPopupHelper.onItemClick(MenuPopupHelper.java:156)
at android.widget.AdapterView.performItemClick(AdapterView.java:298)
at android.widget.AbsListView.performItemClick(AbsListView.java:1100)
at android.widget.AbsListView$PerformClick.run(AbsListView.java:2749)
at android.widget.AbsListView$1.run(AbsListView.java:3423)
at android.os.Handler.handleCallback(Handler.java:725)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5041)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
at dalvik.system.NativeStart.main(Native Method)
After ADT 22 the PagerAdapter has gotten very strict about calling notifyDataSetChanged() before calling getCount(). It evidently keeps track of what it thinks the count should be and if this is not the same as what getCount() returns it throws this exception. So the solution is simply to call notifyDataSetChanged() on the adapter every time the size of the data changes.
In my case I have a background task that is creating an array of data. After the array is filled I call notifyDataSetChanged() in onPostExecute(). When I had a path in onPostExecute() that did not call notifyDataSetChanged() I was getting the exception.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With