I have one fragment where are three (default) images and when user click on them, they will change to another. But when i swipe to another fragment and back to fragment with images there are not default ones as on the start. When I swipe two times so I will pass to another fragment (distance from original with images is 2 fragments) images are resetted to default. I was trying to implement setOffscreenPageLimit() from ViewPager and set it to 1, but minimum "length" when views in fragments are resetted is 2. How can I change that images to default manually after swipe action? Thank you.
Edit: I think that issue why onResume() is not working here: Fragment onResume not called
but i dont know what that means :/ I have three classes FragmentController.class, PagerAdapter.class and class of specific fragment for example FirstFragment.class. I don't know how connect these classes together.
Check that you create the fragments in the getItem() method of the adapter and do not hold any reference to that fragments (only WeakReferences if necessary), otherwise the fragments could not be destroyed.
EDIT:
The first fragment is unloaded only when you are in the third one because setOffscreenPageLimit is at least 1 so a viewpager allways loads the fragments that are at both sides of the selected one.
What you could do is to update your adapter with this code to provide a getFragment(position) method:
private HashMap<Integer, WeakReference<Fragment>> fragmentReferences = new HashMap<>();
#Override
public Fragment getItem(int position) {
Fragment fragment;
switch (position) {
case 0:
fragment = FirstFragment.newInstance();
break;
// etc
}
fragmentReferences.put(position, new WeakReference<>(fragment));
return fragment;
}
public Fragment getFragment(int position) {
WeakReference<Fragment> ref = fragmentReferences.get(position);
return ref == null ? null : ref.get();
}
After then you can get the selected fragment and call the method you want from the first fragment when a page is selected:
viewPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int currentPage) {
if (currentPage == 0) {
Fragment firstFragment = adapter.getFragment(0);
if (firstFragment != null) {
// This method resets the images of the first fragment
((FirstFragment) firstFragment).reset();
}
}
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// Do nothing
}
#Override
public void onPageScrollStateChanged(int state) {
// Do nothing
}
});
I have 3 swipes tabs(A,B,C) in my android. When comes to tabs c, I have a function RetrieveData(), used to retrieve the data from MySQL and load into listView.
Assume in MySQL I have one row data only, when comes to c, the data will be loaded into listView (one list only). When I swipe to Tab A and swipe to C again, the list become 2 now.
Is there a way I can make the RetrieveData() only call one times ? Thanks.
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View edit_details = inflater.inflate(R.layout.edit_work_details, container, false);
// EditDetails = new ArrayList<HashMap<String, String>>();
listViewUpdate = (ListView) edit_details.findViewById(R.id.listViewEdit);
totalHours=(TextView)edit_details.findViewById(R.id.hour);
setHasOptionsMenu(true);
Bundle bundle = this.getArguments();
if (getArguments() != null) {
ID = bundle.getString("ID");
RetrieveData(ID); // retrieve data from MySQL
}
Toast.makeText(getActivity(), "Details" + ID, Toast.LENGTH_LONG).show();
}
TabAdapter
public class TabsFragmentPagerAdapter extends FragmentPagerAdapter {
public TabsFragmentPagerAdapter(FragmentManager fm) {
super(fm);
// TODO Auto-generated constructor stub
}
#Override
public Fragment getItem(int index) {
// TODO Auto-generated method stub
if(index == 0) {
Fragment fragment=new A();
Bundle bundle = new Bundle();
bundle.putString("ID", Edit.ID);
fragment.setArguments(bundle);
return fragment;
}
if(index == 1) {
Fragment fragment = new B();
Bundle bundle = new Bundle();
bundle.putString("ID", Edit.ID);
fragment.setArguments(bundle);
return fragment;
}
if(index == 2) {
Fragment fragment = new C();
Bundle bundle = new Bundle();
bundle.putString("ID", Edit.ID);
fragment.setArguments(bundle);
return fragment;
}
return null;
}
#Override
public int getCount() {
// TODO Auto-generated method stub
return 3;
}
}
ViewPager code
public class ActivityB extends ActionBarActivity implements ActionBar.TabListener {
private ViewPager viewPager;
private ActionBar actionBar;
private TabsFragmentPagerAdapter tabsAdapter;
private String[] item = new String[]{"Information","Work Force","Work Details"};
private String id;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b);
// id=getIntent().getExtras().getString("ID");
viewPager = (ViewPager) findViewById(R.id.viewPager);
tabsAdapter = new TabsFragmentPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(tabsAdapter);
actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
for(int i=0; i<4; i++){
actionBar.addTab(actionBar.newTab().setText(item[i]).setTabListener(this));
}
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int arg) {
// TODO Auto-generated method stub
actionBar.setSelectedNavigationItem(arg);
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
// TODO Auto-generated method stub
}
#Override
public void onPageScrollStateChanged(int arg0) {
// TODO Auto-generated method stub
}
});
}
#Override
public void onTabReselected(ActionBar.Tab arg0, FragmentTransaction arg1) {
// TODO Auto-generated method stub
}
#Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction arg1) {
// TODO Auto-generated method stub
viewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(ActionBar.Tab arg0, FragmentTransaction arg1) {
// TODO Auto-generated method stub
}
}
This is my tab c
After swipe to A and to C again
Seems like it calling RetrieveData again.
You should use setOffscreenPageLimit of ViewPager. It will set how many pages of pager you want to load at first.
Bydefault value for setOffScreeenPageLimit is 1, so it will load current page and next page. In your case it will load A and B, when you will move to C from B it will destroy instance of A.
try using
`pager.setOffscreenPageLimit(2)`
it will load current page and 2 more pages so A,B and C will be loaded once .
EDIT
Set the number of pages that should be retained to either side of the
current page in the view hierarchy in an idle state. Pages beyond this
limit will be recreated from the adapter when needed.
This is offered as an optimization. If you know in advance the number
of pages you will need to support or have lazy-loading mechanisms in
place on your pages, tweaking this setting can have benefits in
perceived smoothness of paging animations and interaction. If you have
a small number of pages (3-4) that you can keep active all at once,
less time will be spent in layout for newly created view subtrees as
the user pages back and forth.
Source
It clearly means you can set number of pages you want to load at initial level, if you want to load 5 pages at first run, set its value to 4.
Now if you will move from any of pages among 5 pages, onCreateView() of Fragment will not be called, as its already loaded at initial stage.
You can prevent this by setting the OffscreenPageLimit to 2 to your ViewPager instance.
Here is the docs -
/**
* Set the number of pages that should be retained to either side of the
* current page in the view hierarchy in an idle state. Pages beyond this
* limit will be recreated from the adapter when needed.
*
* <p>This is offered as an optimization. If you know in advance the number
* of pages you will need to support or have lazy-loading mechanisms in place
* on your pages, tweaking this setting can have benefits in perceived smoothness
* of paging animations and interaction. If you have a small number of pages (3-4)
* that you can keep active all at once, less time will be spent in layout for
* newly created view subtrees as the user pages back and forth.</p>
*
* <p>You should keep this limit low, especially if your pages have complex layouts.
* This setting defaults to 1.</p>
*
* #param limit How many pages will be kept offscreen in an idle state.
*/
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
If you're retrieving from the DB every time you swipe to C, then you need to clear the list before hand, either in the onResume method when you enter the fragment, or in the onPause method when you leave the fragment.
This mean either clear your ArrayList for example, and make sure its not static, or else it means you have to clear your ListView by using listView.setAdapter(null);. Then don't forget to use notififyDatasetChanged() on the adapter.
Since you haven't pasted your RetrieveData() code, I assume you read the data into an ArrayList<> or iterate over the cursor.And also there are no other places where you are calling this method except onCreateVie(). Some of the possible solutions may be:
If you are explicitly setting viewPager.setOffscreenPageLimit(x), where x>1, remove that code. By default this value is 1 and view Pager calls createView every time.
As you are using FragmentPagerAdapter, once you have loaded your C fragment ideally , it should not be created again, since it should be present in memory and ViewPager will be using that.Check if your onCreateView() is being called every time for C fragment.
Please make sure while retrieving your data into a list every time, you are not appending into list instead of clearing the list and then adding it.
Please try above.
Many answers suggest setOffscreenPageLimit but I wouldn't recommend it for 2 reasons.
1) Resources of a fragment in viewpager will not be garbage collected even if it's out of screen. This will cause memory issues in case you have objects taking memory like bitmaps.
2) This approach will not be enough when app goes to background and comes to foreground again.
I suggest saving your data in onSaveInstanceState in each fragment in your ViewPager and check if there is a data saved in onCreate. If there is data, show it. Otherwise, query for the data.
I have faced a similar problem sometime back, issue was with my code, on fetching date from DB, i was populating the list, and whenever page refreshes it will not creating a new list, it was directly adding the same data in the list which already contains the data and shows two row with same entry.
To solve this problem, I have cleared my list before populating the data.
I use a FragmentPagerAdapter to switch from fragments. I need some functions to be called when a fragmentswitch is made and had some troubles with OnPause and OnResume, so as suggested by THIS question I have implemented an interface OnPageSelectListener :
public interface OnPageSelectListener {
void onPageSelected();
void onPageNotVisible();
}
It calls the function OnPageSelected whenever this page comes to the foreground. Works nice, except that I want to call a function on my adapter. I thought that would work, except that my adapter returns NULL all the times (even though it is initialized and data is loaded in my listview as prefered).
public class AfterCheckFragment extends Fragment implements OnPageSelectListener{
private ListView listView;
private List<Check> checkList;
private CheckListAdapter adapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_check, container, false);
System.out.println("VIEW create called");
//(.. some other stuff, not relevant for question..)
//initializing the adapter
listView = (ListView) view.findViewById(R.id.listView);
adapter = new CheckListAdapter(checkList,getActivity(),trainPosition);
listView.setAdapter(adapter);
adapter.handleButtonVisibility();
return view;
}
#Override
public void onPageSelected() {
if(this.adapter != null) {
System.out.println("adapter not null");
this.adapter.checkForActive();
}else{
System.out.println("Adapter is NULL");
}
}
#Override
public void onPageNotVisible() { //page is moved to backgroung
System.out.println("AFTER not active any more ");
}
}
Now is my question: Why does adapter (or any other object in the fragment) return null when I return to my fragment? When the fragmentPager is initialized the onActivityCreate function of the fragment is called one time, but after that not any more, and the adapter return null....
you have to call the onPageSelected() after initialization of the adapter and setAdapter() otherwise adapter will return null always
Here is why I think your CheckListAdapter (i'll call it listAdapter) is null:
You give the pagerAdapter to the ViewPager
The ViewPager asks the pagerAdapter for a new Fragment
The ViewPager tells the FragmentManager to use it
onPageSelected gets called
You try and use listAdapter. It hasn't been initialized yet at this point. (NPE)
The FragmentManager drags the Fragment through all its stages.
onCreateView gets called. Your listAdapter is created.
Don't try and use internal data of a fragment outside of it. It is meant to work as a standalone unit, it won't be very good if you use it differently. Since the fragment is initialized at a later stage, you can't use it like you intend.
You can try and do what you want to do in the fragment, rather than the pagerAdapter, or write a method in the hosting Activity and call it from the fragment when ready, or even launch an event.
ViewPager will create and destroy fragments as the user changes pages (see ViewPager.setOffscreenPageLimit()). So onActivityCreated() is only called on the fragment when it is being restored or set up for the first time. Hence, fragments can be created without ever having onActivityCreated() called.
Instead of onActivityCreated(), I would recommend overriding onViewCreated() and setting up your adapter there. No fragment can be displayed without having a view created, so this is a good place to do that kind of stuff.
If you have your OnPageSelectListener logic working, that's good. I found the best way to know when your fragment is actually in front of the user is by overriding setPrimaryItem() in the FragmentPagerAdapter. Getting the page out of view event is a little trickier, since you have to keep a reference to the fragment from the previous setPrimaryItem() call.
This is because Viewpager calls OnpageSelected way before Fragments in oncreateView()/onActivityCreated() is called .
The best way for you is to inflate your views in the constructor of the Fragment and set the Adapters.
Or
Use a member variable to store whether the Fragment is active or not. And use the variable in oncreateview() to call function on your adapter.
Why don't you use a viewpager.addOnPageChangeListener, in you pager , after setting its adapter and the setOffscreenPageLimit() instead of implements it on your fragment?
Heres a sample code:
viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
if(position == 1){ // if you want the second page, for example
//Your code here
}
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
Make it in your Activity, where you setup your ViewPager, of course.
for me i had to call this on my viewpager:
myViewPager.setSaveFromParentEnabled(false);
I am reusing the same fragment to display an image with an animation. The problem which I facing is the fragment view is loaded with the animation before it is visible to the user while swiping. Below is my PagerAdapter
public class PollsAdapter extends FragmentStatePagerAdapter {
/** Constructor of the class */
int clickPosition = 0;
ArrayList<Polls> pollsList = new ArrayList<Polls>();
public PollsAdapter(FragmentManager fm,ArrayList<Polls> pollsList) {
super(fm);
this.pollsList=pollsList;
}
/** This method will be invoked when a page is requested to create */
#Override
public Fragment getItem(int arg0) {
PollsFragment myFragment = new PollsFragment();
Bundle data = new Bundle();
data.putSerializable("poll", pollsList.get(arg0));
data.putInt("position", arg0);
myFragment.setArguments(data);
return myFragment;
}
/** Returns the number of pages */
#Override
public int getCount() {
return pollsList.size();
}
public int getItemPosition(Object item) {
return POSITION_NONE;
}
}
This is how I set my viewPager
pollsAdapter = new PollsAdapter(fm, pollsList);
viewPager = (ViewPager) _rootView.findViewById(R.id.pager);
viewPager.setAdapter(pollsAdapter);
I tried by using `viewPager.setOffscreenPageLimit(1); but this didn't work. I'm sure I miss something. Please help me to solve this problem.
This is --unfortunately-- not possible. To display the flip animation, ViewPager has to pre-load at least the fragments to the left/right of the currently displayed fragment.
Because of this, the minimum possible value for ViewPager.setOffscreenPageLimit() is 1 as at least the direct neighbours need to be available.
In your case, the problem is rather that your fragment seems to be starting an animation as soon at is created.
In that case, you will need to change the starting logic for your animation. Your fragment can get notified by the ViewPager as soon as it is scrolled to (see ViewPager.OnPageChangeListener) for the mechanism). Note that the OnPageListener is not called when your activity is resumed so you also need to handle this case..
How to determine when Fragment becomes visible in ViewPager has more information on that topic.
Fragments seem to be very nice for separation of UI logic into some modules. But along with ViewPager its lifecycle is still misty to me. So Guru thoughts are badly needed!
Edit
See dumb solution below ;-)
Scope
Main activity has a ViewPager with fragments. Those fragments could implement a little bit different logic for other (submain) activities, so the fragments' data is filled via a callback interface inside the activity. And everything works fine on first launch, but!...
Problem
When the activity gets recreated (e.g. on orientation change) so do the ViewPager's fragments. The code (you'll find below) says that every time the activity is created I try to create a new ViewPager fragments adapter the same as fragments (maybe this is the problem) but FragmentManager already has all these fragments stored somewhere (where?) and starts the recreation mechanism for those. So the recreation mechanism calls the "old" fragment's onAttach, onCreateView, etc. with my callback interface call for initiating data via the Activity's implemented method. But this method points to the newly created fragment which is created via the Activity's onCreate method.
Issue
Maybe I'm using wrong patterns but even Android 3 Pro book doesn't have much about it. So, please, give me one-two punch and point out how to do it the right way. Many thanks!
Code
Main Activity
public class DashboardActivity extends BasePagerActivity implements OnMessageListActionListener {
private MessagesFragment mMessagesFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
Logger.d("Dash onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.viewpager_container);
new DefaultToolbar(this);
// create fragments to use
mMessagesFragment = new MessagesFragment();
mStreamsFragment = new StreamsFragment();
// set titles and fragments for view pager
Map<String, Fragment> screens = new LinkedHashMap<String, Fragment>();
screens.put(getApplicationContext().getString(R.string.dashboard_title_dumb), new DumbFragment());
screens.put(getApplicationContext().getString(R.string.dashboard_title_messages), mMessagesFragment);
// instantiate view pager via adapter
mPager = (ViewPager) findViewById(R.id.viewpager_pager);
mPagerAdapter = new BasePagerAdapter(screens, getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
// set title indicator
TitlePageIndicator indicator = (TitlePageIndicator) findViewById(R.id.viewpager_titles);
indicator.setViewPager(mPager, 1);
}
/* set of fragments callback interface implementations */
#Override
public void onMessageInitialisation() {
Logger.d("Dash onMessageInitialisation");
if (mMessagesFragment != null)
mMessagesFragment.loadLastMessages();
}
#Override
public void onMessageSelected(Message selectedMessage) {
Intent intent = new Intent(this, StreamActivity.class);
intent.putExtra(Message.class.getName(), selectedMessage);
startActivity(intent);
}
BasePagerActivity aka helper
public class BasePagerActivity extends FragmentActivity {
BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}
Adapter
public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {
private Map<String, Fragment> mScreens;
public BasePagerAdapter(Map<String, Fragment> screenMap, FragmentManager fm) {
super(fm);
this.mScreens = screenMap;
}
#Override
public Fragment getItem(int position) {
return mScreens.values().toArray(new Fragment[mScreens.size()])[position];
}
#Override
public int getCount() {
return mScreens.size();
}
#Override
public String getTitle(int position) {
return mScreens.keySet().toArray(new String[mScreens.size()])[position];
}
// hack. we don't want to destroy our fragments and re-initiate them after
#Override
public void destroyItem(View container, int position, Object object) {
// TODO Auto-generated method stub
}
}
Fragment
public class MessagesFragment extends ListFragment {
private boolean mIsLastMessages;
private List<Message> mMessagesList;
private MessageArrayAdapter mAdapter;
private LoadMessagesTask mLoadMessagesTask;
private OnMessageListActionListener mListener;
// define callback interface
public interface OnMessageListActionListener {
public void onMessageInitialisation();
public void onMessageSelected(Message selectedMessage);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// setting callback
mListener = (OnMessageListActionListener) activity;
mIsLastMessages = activity instanceof DashboardActivity;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
inflater.inflate(R.layout.fragment_listview, container);
mProgressView = inflater.inflate(R.layout.listrow_progress, null);
mEmptyView = inflater.inflate(R.layout.fragment_nodata, null);
return super.onCreateView(inflater, container, savedInstanceState);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// instantiate loading task
mLoadMessagesTask = new LoadMessagesTask();
// instantiate list of messages
mMessagesList = new ArrayList<Message>();
mAdapter = new MessageArrayAdapter(getActivity(), mMessagesList);
setListAdapter(mAdapter);
}
#Override
public void onResume() {
mListener.onMessageInitialisation();
super.onResume();
}
public void onListItemClick(ListView l, View v, int position, long id) {
Message selectedMessage = (Message) getListAdapter().getItem(position);
mListener.onMessageSelected(selectedMessage);
super.onListItemClick(l, v, position, id);
}
/* public methods to load messages from host acitivity, etc... */
}
Solution
The dumb solution is to save the fragments inside onSaveInstanceState (of host Activity) with putFragment and get them inside onCreate via getFragment. But I still have a strange feeling that things shouldn't work like that... See code below:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager()
.putFragment(outState, MessagesFragment.class.getName(), mMessagesFragment);
}
protected void onCreate(Bundle savedInstanceState) {
Logger.d("Dash onCreate");
super.onCreate(savedInstanceState);
...
// create fragments to use
if (savedInstanceState != null) {
mMessagesFragment = (MessagesFragment) getSupportFragmentManager().getFragment(
savedInstanceState, MessagesFragment.class.getName());
StreamsFragment.class.getName());
}
if (mMessagesFragment == null)
mMessagesFragment = new MessagesFragment();
...
}
When the FragmentPagerAdapter adds a fragment to the FragmentManager, it uses a special tag based on the particular position that the fragment will be placed. FragmentPagerAdapter.getItem(int position) is only called when a fragment for that position does not exist. After rotating, Android will notice that it already created/saved a fragment for this particular position and so it simply tries to reconnect with it with FragmentManager.findFragmentByTag(), instead of creating a new one. All of this comes free when using the FragmentPagerAdapter and is why it is usual to have your fragment initialisation code inside the getItem(int) method.
Even if we were not using a FragmentPagerAdapter, it is not a good idea to create a new fragment every single time in Activity.onCreate(Bundle). As you have noticed, when a fragment is added to the FragmentManager, it will be recreated for you after rotating and there is no need to add it again. Doing so is a common cause of errors when working with fragments.
A usual approach when working with fragments is this:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
CustomFragment fragment;
if (savedInstanceState != null) {
fragment = (CustomFragment) getSupportFragmentManager().findFragmentByTag("customtag");
} else {
fragment = new CustomFragment();
getSupportFragmentManager().beginTransaction().add(R.id.container, fragment, "customtag").commit();
}
...
}
When using a FragmentPagerAdapter, we relinquish fragment management to the adapter, and do not have to perform the above steps. By default, it will only preload one Fragment in front and behind the current position (although it does not destroy them unless you are using FragmentStatePagerAdapter). This is controlled by ViewPager.setOffscreenPageLimit(int). Because of this, directly calling methods on the fragments outside of the adapter is not guaranteed to be valid, because they may not even be alive.
To cut a long story short, your solution to use putFragment to be able to get a reference afterwards is not so crazy, and not so unlike the normal way to use fragments anyway (above). It is difficult to obtain a reference otherwise because the fragment is added by the adapter, and not you personally. Just make sure that the offscreenPageLimit is high enough to load your desired fragments at all times, since you rely on it being present. This bypasses lazy loading capabilities of the ViewPager, but seems to be what you desire for your application.
Another approach is to override FragmentPageAdapter.instantiateItem(View, int) and save a reference to the fragment returned from the super call before returning it (it has the logic to find the fragment, if already present).
For a fuller picture, have a look at some of the source of FragmentPagerAdapter (short) and ViewPager (long).
I want to offer a solution that expands on antonyt's wonderful answer and mention of overriding FragmentPageAdapter.instantiateItem(View, int) to save references to created Fragments so you can do work on them later. This should also work with FragmentStatePagerAdapter; see notes for details.
Here's a simple example of how to get a reference to the Fragments returned by FragmentPagerAdapter that doesn't rely on the internal tags set on the Fragments. The key is to override instantiateItem() and save references in there instead of in getItem().
public class SomeActivity extends Activity {
private FragmentA m1stFragment;
private FragmentB m2ndFragment;
// other code in your Activity...
private class CustomPagerAdapter extends FragmentPagerAdapter {
// other code in your custom FragmentPagerAdapter...
public CustomPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
// Do NOT try to save references to the Fragments in getItem(),
// because getItem() is not always called. If the Fragment
// was already created then it will be retrieved from the FragmentManger
// and not here (i.e. getItem() won't be called again).
switch (position) {
case 0:
return new FragmentA();
case 1:
return new FragmentB();
default:
// This should never happen. Always account for each position above
return null;
}
}
// Here we can finally safely save a reference to the created
// Fragment, no matter where it came from (either getItem() or
// FragmentManger). Simply save the returned Fragment from
// super.instantiateItem() into an appropriate reference depending
// on the ViewPager position.
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
// save the appropriate reference depending on position
switch (position) {
case 0:
m1stFragment = (FragmentA) createdFragment;
break;
case 1:
m2ndFragment = (FragmentB) createdFragment;
break;
}
return createdFragment;
}
}
public void someMethod() {
// do work on the referenced Fragments, but first check if they
// even exist yet, otherwise you'll get an NPE.
if (m1stFragment != null) {
// m1stFragment.doWork();
}
if (m2ndFragment != null) {
// m2ndFragment.doSomeWorkToo();
}
}
}
or if you prefer to work with tags instead of class member variables/references to the Fragments you can also grab the tags set by FragmentPagerAdapter in the same manner:
NOTE: this doesn't apply to FragmentStatePagerAdapter since it doesn't set tags when creating its Fragments.
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
// get the tags set by FragmentPagerAdapter
switch (position) {
case 0:
String firstTag = createdFragment.getTag();
break;
case 1:
String secondTag = createdFragment.getTag();
break;
}
// ... save the tags somewhere so you can reference them later
return createdFragment;
}
Note that this method does NOT rely on mimicking the internal tag set by FragmentPagerAdapter and instead uses proper APIs for retrieving them. This way even if the tag changes in future versions of the SupportLibrary you'll still be safe.
Don't forget that depending on the design of your Activity, the Fragments you're trying to work on may or may not exist yet, so you have to account for that by doing null checks before using your references.
Also, if instead you're working with FragmentStatePagerAdapter, then you don't want to keep hard references to your Fragments because you might have many of them and hard references would unnecessarily keep them in memory. Instead save the Fragment references in WeakReference variables instead of standard ones. Like this:
WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
// reference hasn't been cleared yet; do work...
}
I found another relatively easy solution for your question.
As you can see from the FragmentPagerAdapter source code, the fragments managed by FragmentPagerAdapter store in the FragmentManager under the tag generated using:
String tag="android:switcher:" + viewId + ":" + index;
The viewId is the container.getId(), the container is your ViewPager instance. The index is the position of the fragment. Hence you can save the object id to the outState:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("viewpagerid" , mViewPager.getId() );
}
#Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
if (savedInstanceState != null)
viewpagerid=savedInstanceState.getInt("viewpagerid", -1 );
MyFragmentPagerAdapter titleAdapter = new MyFragmentPagerAdapter (getSupportFragmentManager() , this);
mViewPager = (ViewPager) findViewById(R.id.pager);
if (viewpagerid != -1 ){
mViewPager.setId(viewpagerid);
}else{
viewpagerid=mViewPager.getId();
}
mViewPager.setAdapter(titleAdapter);
If you want to communicate with this fragment, you can get if from FragmentManager, such as:
getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewpagerid + ":0")
I want to offer an alternate solution for perhaps a slightly different case, since many of my searches for answers kept leading me to this thread.
My case
- I'm creating/adding pages dynamically and sliding them into a ViewPager, but when rotated (onConfigurationChange) I end up with a new page because of course OnCreate is called again. But I want to keep reference to all the pages that were created prior to the rotation.
Problem
- I don't have unique identifiers for each fragment I create, so the only way to reference was to somehow store references in an Array to be restored after the rotation/configuration change.
Workaround
- The key concept was to have the Activity (which displays the Fragments) also manage the array of references to existing Fragments, since this activity can utilize Bundles in onSaveInstanceState
public class MainActivity extends FragmentActivity
So within this Activity, I declare a private member to track the open pages
private List<Fragment> retainedPages = new ArrayList<Fragment>();
This is updated everytime onSaveInstanceState is called and restored in onCreate
#Override
protected void onSaveInstanceState(Bundle outState) {
retainedPages = _adapter.exportList();
outState.putSerializable("retainedPages", (Serializable) retainedPages);
super.onSaveInstanceState(outState);
}
...so once it's stored, it can be retrieved...
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null) {
retainedPages = (List<Fragment>) savedInstanceState.getSerializable("retainedPages");
}
_mViewPager = (CustomViewPager) findViewById(R.id.viewPager);
_adapter = new ViewPagerAdapter(getApplicationContext(), getSupportFragmentManager());
if (retainedPages.size() > 0) {
_adapter.importList(retainedPages);
}
_mViewPager.setAdapter(_adapter);
_mViewPager.setCurrentItem(_adapter.getCount()-1);
}
These were the necessary changes to the main activity, and so I needed the members and methods within my FragmentPagerAdapter for this to work, so within
public class ViewPagerAdapter extends FragmentPagerAdapter
an identical construct (as shown above in MainActivity )
private List<Fragment> _pages = new ArrayList<Fragment>();
and this syncing (as used above in onSaveInstanceState) is supported specifically by the methods
public List<Fragment> exportList() {
return _pages;
}
public void importList(List<Fragment> savedPages) {
_pages = savedPages;
}
And then finally, in the fragment class
public class CustomFragment extends Fragment
in order for all this to work, there were two changes, first
public class CustomFragment extends Fragment implements Serializable
and then adding this to onCreate so Fragments aren't destroyed
setRetainInstance(true);
I'm still in the process of wrapping my head around Fragments and Android life cycle, so caveat here is there may be redundancies/inefficiencies in this method. But it works for me and I hope might be helpful for others with cases similar to mine.
My solution is very rude but works: being my fragments dynamically created from retained data, I simply remove all fragment from the PageAdapter before calling super.onSaveInstanceState() and then recreate them on activity creation:
#Override
protected void onSaveInstanceState(Bundle outState) {
outState.putInt("viewpagerpos", mViewPager.getCurrentItem() );
mSectionsPagerAdapter.removeAllfragments();
super.onSaveInstanceState(outState);
}
You can't remove them in onDestroy(), otherwise you get this exception:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
Here the code in the page adapter:
public void removeAllfragments()
{
if ( mFragmentList != null ) {
for ( Fragment fragment : mFragmentList ) {
mFm.beginTransaction().remove(fragment).commit();
}
mFragmentList.clear();
notifyDataSetChanged();
}
}
I only save the current page and restore it in onCreate(), after the fragments have been created.
if (savedInstanceState != null)
mViewPager.setCurrentItem( savedInstanceState.getInt("viewpagerpos", 0 ) );
What is that BasePagerAdapter? You should use one of the standard pager adapters -- either FragmentPagerAdapter or FragmentStatePagerAdapter, depending on whether you want Fragments that are no longer needed by the ViewPager to either be kept around (the former) or have their state saved (the latter) and re-created if needed again.
Sample code for using ViewPager can be found here
It is true that the management of fragments in a view pager across activity instances is a little complicated, because the FragmentManager in the framework takes care of saving the state and restoring any active fragments that the pager has made. All this really means is that the adapter when initializing needs to make sure it re-connects with whatever restored fragments there are. You can look at the code for FragmentPagerAdapter or FragmentStatePagerAdapter to see how this is done.
If anyone is having issues with their FragmentStatePagerAdapter not properly restoring the state of its fragments...ie...new Fragments are being created by the FragmentStatePagerAdapter instead of it restoring them from state...
Make sure you call ViewPager.setOffscreenPageLimit() BEFORE you call ViewPager.setAdapter(fragmentStatePagerAdapter)
Upon calling ViewPager.setOffscreenPageLimit()...the ViewPager will immediately look to its adapter and try to get its fragments. This could happen before the ViewPager has a chance to restore the Fragments from savedInstanceState(thus creating new Fragments that can't be re-initialized from SavedInstanceState because they're new).
I came up with this simple and elegant solution. It assumes that the activity is responsible for creating the Fragments, and the Adapter just serves them.
This is the adapter's code (nothing weird here, except for the fact that mFragments is a list of fragments maintained by the Activity)
class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
#Override
public int getCount() {
return mFragments.size();
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
#Override
public CharSequence getPageTitle(int position) {
TabFragment fragment = (TabFragment)mFragments.get(position);
return fragment.getTitle();
}
}
The whole problem of this thread is getting a reference of the "old" fragments, so I use this code in the Activity's onCreate.
if (savedInstanceState!=null) {
if (getSupportFragmentManager().getFragments()!=null) {
for (Fragment fragment : getSupportFragmentManager().getFragments()) {
mFragments.add(fragment);
}
}
}
Of course you can further fine tune this code if needed, for example making sure the fragments are instances of a particular class.
To get the fragments after orientation change you have to use the .getTag().
getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewPagerId + ":" + positionOfItemInViewPager)
For a bit more handling i wrote my own ArrayList for my PageAdapter to get the fragment by viewPagerId and the FragmentClass at any Position:
public class MyPageAdapter extends FragmentPagerAdapter implements Serializable {
private final String logTAG = MyPageAdapter.class.getName() + ".";
private ArrayList<MyPageBuilder> fragmentPages;
public MyPageAdapter(FragmentManager fm, ArrayList<MyPageBuilder> fragments) {
super(fm);
fragmentPages = fragments;
}
#Override
public Fragment getItem(int position) {
return this.fragmentPages.get(position).getFragment();
}
#Override
public CharSequence getPageTitle(int position) {
return this.fragmentPages.get(position).getPageTitle();
}
#Override
public int getCount() {
return this.fragmentPages.size();
}
public int getItemPosition(Object object) {
//benötigt, damit bei notifyDataSetChanged alle Fragemnts refrehsed werden
Log.d(logTAG, object.getClass().getName());
return POSITION_NONE;
}
public Fragment getFragment(int position) {
return getItem(position);
}
public String getTag(int position, int viewPagerId) {
//getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.shares_detail_activity_viewpager + ":" + myViewPager.getCurrentItem())
return "android:switcher:" + viewPagerId + ":" + position;
}
public MyPageBuilder getPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
return new MyPageBuilder(pageTitle, icon, selectedIcon, frag);
}
public static class MyPageBuilder {
private Fragment fragment;
public Fragment getFragment() {
return fragment;
}
public void setFragment(Fragment fragment) {
this.fragment = fragment;
}
private String pageTitle;
public String getPageTitle() {
return pageTitle;
}
public void setPageTitle(String pageTitle) {
this.pageTitle = pageTitle;
}
private int icon;
public int getIconUnselected() {
return icon;
}
public void setIconUnselected(int iconUnselected) {
this.icon = iconUnselected;
}
private int iconSelected;
public int getIconSelected() {
return iconSelected;
}
public void setIconSelected(int iconSelected) {
this.iconSelected = iconSelected;
}
public MyPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
this.pageTitle = pageTitle;
this.icon = icon;
this.iconSelected = selectedIcon;
this.fragment = frag;
}
}
public static class MyPageArrayList extends ArrayList<MyPageBuilder> {
private final String logTAG = MyPageArrayList.class.getName() + ".";
public MyPageBuilder get(Class cls) {
// Fragment über FragmentClass holen
for (MyPageBuilder item : this) {
if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
return super.get(indexOf(item));
}
}
return null;
}
public String getTag(int viewPagerId, Class cls) {
// Tag des Fragment unabhängig vom State z.B. nach bei Orientation change
for (MyPageBuilder item : this) {
if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
return "android:switcher:" + viewPagerId + ":" + indexOf(item);
}
}
return null;
}
}
So just create a MyPageArrayList with the fragments:
myFragPages = new MyPageAdapter.MyPageArrayList();
myFragPages.add(new MyPageAdapter.MyPageBuilder(
getString(R.string.widget_config_data_frag),
R.drawable.ic_sd_storage_24dp,
R.drawable.ic_sd_storage_selected_24dp,
new WidgetDataFrag()));
myFragPages.add(new MyPageAdapter.MyPageBuilder(
getString(R.string.widget_config_color_frag),
R.drawable.ic_color_24dp,
R.drawable.ic_color_selected_24dp,
new WidgetColorFrag()));
myFragPages.add(new MyPageAdapter.MyPageBuilder(
getString(R.string.widget_config_textsize_frag),
R.drawable.ic_settings_widget_24dp,
R.drawable.ic_settings_selected_24dp,
new WidgetTextSizeFrag()));
and add them to the viewPager:
mAdapter = new MyPageAdapter(getSupportFragmentManager(), myFragPages);
myViewPager.setAdapter(mAdapter);
after this you can get after orientation change the correct fragment by using its class:
WidgetDataFrag dataFragment = (WidgetDataFrag) getSupportFragmentManager()
.findFragmentByTag(myFragPages.getTag(myViewPager.getId(), WidgetDataFrag.class));
A bit different opinion instead of storing the Fragments yourself just leave it to the FragmentManager and when you need to do something with the fragments look for them in the FragmentManager:
//make sure you have the right FragmentManager
//getSupportFragmentManager or getChildFragmentManager depending on what you are using to manage this stack of fragments
List<Fragment> fragments = fragmentManager.getFragments();
if(fragments != null) {
int count = fragments.size();
for (int x = 0; x < count; x++) {
Fragment fragment = fragments.get(x);
//check if this is the fragment we want,
//it may be some other inspection, tag etc.
if (fragment instanceof MyFragment) {
//do whatever we need to do with it
}
}
}
If you have a lot of Fragments and the cost of instanceof check may be not what you want, but it is good thing to have in mind that the FragmentManager already keeps account of Fragments.
add:
#SuppressLint("ValidFragment")
before your class.
it it doesn´t work do something like this:
#SuppressLint({ "ValidFragment", "HandlerLeak" })