I am using an ActionBarSherlock example to build on. I have 3 fragments (each extends SherlockListFragment
and implement LoaderManager.LoaderCallbacks<>
), 1 in each tab. They all use loaders. Tabs 2 and 3 use the same loader class (I don't think this is a problem).
I add the tabs to my FragmentPagerAdapter
with class references to the different fragments, along with bundles if needed. When my application starts and tab 0 is displayed, getItem in FragmentPagerAdapter
gets tab 0 and tab 1 ready by creating their fragments. Tab 2 isn't created yet. When I SWIPE to tab 1, tab 2 is loaded. When I swipe again, tab 2 is displayed correctly.
The problem occurs when I click from tab 0 to tab 2. getItem
does create the fragment, and the loader starts its doInBackground
processing, however tab 2's fragment never gets the data in onLoadFinished and so sits with the indefinite progressbar.
Something stranger happened earlier. If I clicked, tab 2's onLoadFinished
would receive the data from tab 0's Loader. When I changed each tab's fragment Loader to use a different ID (I don't think they should collide even if they have the same ID) that problem went away. Eg: getLoaderManager().initLoader(0, null, this)
, getLoaderManager().initLoader(1, null, this)
, etc. I'm not sure if that just illustrates my ignorance of Loaders or something else.
My main question is, can I force the FragmentPagerAdapter
to load all the tabs at once? Even doing that probably wouldn't entirely eliminate this bug in my code.
Secondary question: when I swipe around and click refresh (see code below regarding what that does) eventually the refresh buttons in the action bar disappear. Any thoughts?
MainActivity (very similar to linked ABS example):
public class MainActivity extends SherlockFragmentActivity{
private static final int NUMBER_OF_STREAMS = 9;
ViewPager mViewPager;
TabHost mTabHost;
TabsAdapter mTabsAdapter;
private JTVApplication mApplication;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Set up application
if (mApplication == null)
this.mApplication = (JTVApplication)getApplication();
mTabHost = (TabHost)findViewById(android.R.id.tabhost);
mTabHost.setup();
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.pager);
// SET UP TABS ADAPTER
mTabsAdapter = new TabsAdapter(this, mTabHost, mViewPager);
mTabsAdapter.addTab(mTabHost.newTabSpec("categories").setIndicator("Categories"), CategoryListFragment.class, null);
mTabsAdapter.addTab(mTabHost.newTabSpec("top_streams").setIndicator("Top Streams"), StreamListFragment.class, StreamListFragment.instanceBundle(null, null, null, null, NUMBER_OF_STREAMS, 0));
mTabsAdapter.addTab(mTabHost.newTabSpec("favorites").setIndicator("Favorites"), FavoriteListFragment.class, null);
if (savedInstanceState != null) {
mTabHost.setCurrentTabByTag(savedInstanceState.getString("tab"));
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("tab", mTabHost.getCurrentTabTag());
}
/**
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public static class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener {
private final Context mContext;
private final TabHost mTabHost;
private final ViewPager mViewPager;
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
static final class TabInfo {
@SuppressWarnings("unused")
private final String tag;
private final Class<?> clss;
private final Bundle args;
TabInfo(String _tag, Class<?> _class, Bundle _args) {
tag = _tag;
clss = _class;
args = _args;
}
}
static class DummyTabFactory implements TabHost.TabContentFactory {
private final Context mContext;
public DummyTabFactory(Context context) {
mContext = context;
}
@Override
public View createTabContent(String tag) {
View v = new View(mContext);
v.setMinimumWidth(0);
v.setMinimumHeight(0);
return v;
}
}
List<Fragment> fragments = new ArrayList<Fragment>();
public TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager pager) {
super(activity.getSupportFragmentManager());
mContext = activity;
mTabHost = tabHost;
mViewPager = pager;
mTabHost.setOnTabChangedListener(this);
mViewPager.setAdapter(this);
mViewPager.setOnPageChangeListener(this);
}
public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
tabSpec.setContent(new DummyTabFactory(mContext));
String tag = tabSpec.getTag();
TabInfo info = new TabInfo(tag, clss, args);
mTabs.add(info);
mTabHost.addTab(tabSpec);
notifyDataSetChanged();
}
@Override
public Fragment getItem(int position) {
TabInfo info = mTabs.get(position);
return Fragment.instantiate(mContext, info.clss.getName(), info.args);
}
@Override
public int getCount() {
return mTabs.size();
}
@Override
public void onPageScrollStateChanged(int arg0) {}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {}
@Override
public void onPageSelected(int position) {
TabWidget widget = mTabHost.getTabWidget();
int oldFocusability = widget.getDescendantFocusability();
widget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
mTabHost.setCurrentTab(position);
widget.setDescendantFocusability(oldFocusability);
}
@Override
public void onTabChanged(String tabId) {
int newPosition = mTabHost.getCurrentTab();
mViewPager.setCurrentItem(newPosition, true);
}
}
}
Tab0 Fragment (very similar to other Fragments so I will omit the others):
public class CategoryListFragment extends SherlockListFragment implements LoaderManager.LoaderCallbacks<List<Category>>{
public List<Category> mCategories;
public List<Stream> mStreams;
private int mLimit;
private CategoryAdapter mCategoryAdapter;
private String mSearchFilter;
private RestClient mCategoryClient;
public List<Category> getCategories() {
return mCategories;
}
public void setCategories(List<Category> categories) {
this.mCategories = categories;
}
public CategoryListFragment() {
}
@Override
public void onCreate(Bundle savedInstance){
super.onCreate(savedInstance);
this.setHasOptionsMenu(true);
if (mCategoryClient == null)
this.mCategoryClient = JTVApi.getCategories();
// Prepare the loader
getLoaderManager().initLoader(0, null, this);
}
@Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
// CREATE REFRESH BUTTON
MenuItem refreshMenu = menu.add("Refresh");
refreshMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
refreshMenu.setIcon(R.drawable.ic_menu_refresh);
refreshMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
CategoryListFragment.this.setListShown(false);
CategoryListFragment.this.getLoaderManager().restartLoader(0, null, CategoryListFragment.this);
return true;
}
});
}
@Override
public void onLoadFinished(Loader<List<Category>> arg0, List<Category> data) {
if (mCategories == null){
mCategoryAdapter = new CategoryAdapter(getActivity(), R.id.listItem, data);
this.setListAdapter(mCategoryAdapter);
} else {
mCategoryAdapter.setData(data);
}
this.setListShown(true);
}
@Override
public void onLoaderReset(Loader<List<Category>> arg0) {
// TODO check for null
mCategoryAdapter.setData(null);
}
@Override
public Loader<List<Category>> onCreateLoader(int arg0, Bundle arg1) {
return new CategoryLoader(getActivity(), mLimit, mCategoryClient);
}
}
Tab0 Loader (very similar to other Loaders so I will omit the others):
public class CategoryLoader extends AsyncTaskLoader<List<Category>> {
JTVApplication mApplication;
RestClient mRestClient;
List<Category> mCategories;
public CategoryLoader(Context context, int limit, RestClient restClient) {
super(context);
this.mRestClient = restClient;
mApplication = (JTVApplication)context.getApplicationContext();
}
@Override
public List<Category> loadInBackground() {
List<Category> categories = null;
Object json;
try {
json = mApplication.getJSON(mRestClient, mRestClient.bypassCache());
if (mRestClient.getApiMethod() == APIMethod.CATEGORY_LIST && json instanceof JSONObject){
JSONObject jsonObject = (JSONObject)json;
categories = Category.parseCategories(jsonObject);
// Get total count of viewers for "All Categories"
int total_viewers = 0;
for (Category category : categories)
total_viewers += category.getViewers_count();
categories.add(new Category(Category.ALL_CATEGORIES, "All Streams", total_viewers, 0, 0, null, "All Streams"));
// Update global categories list with this data
mApplication.setCategories(categories);
return categories;
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/**
* Called when there is new data to deliver to the client. The
* super class will take care of delivering it; the implementation
* here just adds a little more logic.
*/
@Override
public void deliverResult(List<Category> data) {
mCategories = data;
if (isStarted()) {
super.deliverResult(data);
}
}
/**
* Handles a request to start the Loader.
*/
@Override
protected void onStartLoading() {
if (mCategories != null){
// If we currently have a result available, deliver it
// immediately.
deliverResult(mCategories);
}
// TODO CHECK FOR NEW DATA OR TIMER
if (mCategories == null){
forceLoad();
}
}
/**
* Handles a request to stop the Loader.
*/
@Override protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
/**
* Handles a request to completely reset the Loader.
*/
@Override
protected void onReset() {
super.onReset();
}
}
The adapters are very basic and I am confident they aren't the problem. If anyone asks I can provide more code but I think this is adequate. Thanks in advance for any help.
UPDATE:
When I simply replace FragmentPagerAdapter
with FragmentStatePagerAdapter
it works. I saw it referenced in FragmentPagerAdapter getItem is not called. What is wrong with my code and using FragmentPagerAdapter
?