I am having trouble implementing a feature in my Android app.
Here’s the setup:
ItemPagerActivity: An activity that contains a fragment that displays a pager.ItemPagerFragment: The fragment containing a pager that loads other fragments. A cursor is used to load the fragments.ItemFragment: The fragment in the pager, which performs an asynchronous task to load its data.
What I want is the following:
- as a I swipe pages, the data in the currently displayed
ItemFragmentis communicated to theItemPagerActivity(specifically, the name of the item will be used as the activity’s title).
I’ve defined a listener in ItemFragment that notifies when the data is loaded:
public class ItemFragment ... {
public interface OnItemLoadedListener {
public void onItemLoaded(Item item);
}
private Collection<OnItemLoadListener> listeners;
private class LoadItemTask extends AsyncTask<...> {
...
public void onPostExecute(Item item) {
notifyItemLoaded(item);
...
}
}
}
If this fragment was wrapped by an Activity, then I could set the activity’s title simply by doing the following:
public class ItemActivity {
public void onCreate(...) {
...
ItemFragment fragment = new ItemFragment();
fragment.registerItemLoadedListener(new ItemLoadedListener() {
public void onItemLoaded(Item item) {
setTitle("Item: " + item.getName());
}
});
...
}
}
So that’s easy enough, and works as expected: when the activity starts, it creates the fragment, which loads the item, which notifies the activity, and the title is updated correctly.
But with ItemPagerFragment, the fragments are loaded pre-emptively: swiping to Fragment 3 may mean that Fragment 4 and Fragment 5 are created. Receiving notifications from the ItemFragment class when items are loaded is not correct here because the fragment displayed may not match the fragment that performed the last load.
Now the ViewPager class has a OnPageChangeListener which could be a solution: when I swipe, this listener is invoked with the current page number. From that page number, I need to (somehow) get the fragment representing that page from the adapter, get the Item data out of the fragment, and notify listeners that the Item is now loaded:
public class ItemPagerFragment ... {
private Collection<OnItemLoadedListener> listeners;
public View onCreateView(...) {
...
ViewPager pager = (ViewPager) view.findViewById(R.id.pager):
pager.setOnPageChangeListener(new OnPageChangeListener() {
public void onPageChange(int pageNumber) {
ItemFragment fragment = getItemFragment(pageNumber);
Item item = fragment.getLoadedItem();
notifyItemLoaded(item);
}
});
...
}
}
The ItemPagerActivity class would then register as a listener on the ItemPagerFragment class as follows:
class ItemPagerActivity ... {
public void onCreate(...) {
...
ItemPagerFragment fragment = new ItemPagerFragment();
fragment.registerOnItemLoadedListener(new OnItemLoadedListener() {
public void onItemLoaded(Item item) {
setTitle("Item: " + item.getName());
}
});
...
}
}
This looks good, but there are a number of problems:
-
The
OnPageChangeListenermay be invoked before a fragment has loaded its data (i.e., the fragment is swiped into view before the item has asynchronously loaded). So the call tofragment.getLoadedItem()may returnnull. -
The
OnPageChangeListeneris not invoked for the initial page (only when a page changes, e.g. after a swipe action) so the activity title will be incorrect for the initial page. -
The
ViewPagerclass allows for only oneOnPageChangeListener. This is a problem because I am also using theViewPageIndicatorlibrary, which wants to assign a listener to theViewPager.
I’m assuming that this pattern (notifying the activity of the data in a fragment that has been swiped into view) might be common, so I am wondering if there are any good solutions for this pattern, and to the three specific problems that I have identified above.
I don’t know if I would call it a pattern but the
OnPageChangeListeneris the way to go.First, your code should handle the “no data available situation” from the start. Your
AsyncTaskswill have the job of loading the data and also update the title only if the fragment for which they are working is the visible one(a position field in theItemFragmenttested against theViewPager‘sgetCurrentItem()method). TheOnPageChangeListenerwill handle the update of the title after the data was loaded, as the user switches between pages and the data is available(it will return null if no data is available). To get theItemFragmentfor a certain position you could use the code below:See above.
I admit I don’t have much knowledge on the
ViewPagerIndicatorlibrary but at a quick look on its site I saw:I don’t see where is the limitation.