I have set up a ViewPager with a custom Adapter. That Adapter caches Fragments it shows. Code looks like this:
#Override
public Fragment getItem(int position) {
Fragment f = getCachedFragment(position);
if (f == null) {
//create and add fragment to cachedFragments
}
System.out.println("getItem: Fragment named:" + mTabs.get(position).clss.getSimpleName() + " with Hash " + f.hashCode());
return f;
}
Everything works fine. Except that the initial Fragments shown by the Fragment don't appear to be created using the getItem() method.
I have verified that 2 different kinds of fragments are being initiated, one with the getItem() method, and another one in a miraculous way I'm not able to explain. The Problem is that properly created one isn't the fragment which is being shown, so trying to access it and manipulate a view on it will fail, since onCreateView() has never been called.
As you can see every fragment created or retrieved is logged in chat with the corresponding hashCode. In addition to that I have added another system.out which tells me about new instances of a fragment.
Now, when I am on index 0 of the ViewPager, the fragment for index 1 is loaded as well, but not using getItem(). My Question is, how is that Fragment for index 1 initiated? I checked ViewPagerAdapter documentation but it doesnt provide any kind of solution since it just says it uses getItem() to get the corresponding Item.
Any pointer in the right direction would be greatly appreciated.
Related
Our application has multiple fragments to show data specific to location. We have used FragmentStatePagerAdapter and ViewPager. User should be able to add new page and delete as well. While adding new page works fine, deleting the existing page shows issues. When I verified I had 3 pages. I deleted the 2nd one from left navigation menu button.When I try to slide to the 3rd page, it shows the view of delted page that is 2nd page. ANd it does not show the 3rd page.
I have Overriden getItem() where I return the new page. I have adjusted the ItemCount when I added/deleted the new page to Adapter. And I called startUpdate() before I add/delete, destroy() to delete any fragment object in a position, finishUpdate() after everything done. And finally notifyDataSetChanged().
Looks like nothing is missed but something. I still see the view of deleted fragment and next to it is blank page or something similar depending on which position's page I delete.
I see Removing fragments from FragmentStatePagerAdapter is something similar but could not help my case. Help me with any clue.
I could solve it. Though the answer by Wize here Replace Fragment inside a ViewPager does not solve my issue, I could find a hint from this. I had to override getItemPosition(Object object) which tells system that particulat 'object' is not-changed/no-more-exist.
I have the list of fragments (references) locally as well which I modify according to the list that FragmentStateAdapter maintians and here is my code for getItemPosition():
#Override
public int getItemPosition(Object object) {
if (object instanceof myFragment) {
if (!mFragments.contains(object)) {
return POSITION_NONE;
} else {
return mFragments.indexOf(object);
}
}
return super.getItemPosition(object);
}
Also, I only called notifyDataSetChanged() after I add/delete fragments. I removed startUpdate(), finishUpdate() all that.I do not knwo where exactly where they are used. But it worked without them.
i got viewpager with 4 tabs .. in each tab there is a fragment.
my first tab is a fragment with a form (for example Users)
after i click save, the data is inserted in the database.
my second tab is another fragment with form but it uses data from the first tab (i am getting it from the database) to populate a spinner.
now after i successfully inserted data from my first tab, in my second tab the spinner is empty. since my db query is implemented in onCreateView() in the second fragment, its called only once when the application is started, so changing between tab 1 i tab2 doesn't starts onCreateView() or even onResume().
The interesting thing for me is that if i go to tab4 and then return to tab2, my data is in my spinner properly, so somehow swiping two tabs away from my current tab refreshing my fragment.
my question is how can i achieve proper refresh to my fragment when onCreateView() is called only once ?
Edit
i tried to put that method psykhi suggested like that:
this.mViewPager.setOffscreenPageLimit(0);
this.mViewPager.setAdapter(this.mPagerAdapter);
this.mViewPager.setOnPageChangeListener(this);
but it's not working for me. Am i missing something ?
The best way that I have discovered to simulate a "setOffscreenPageLimit(0)" behavior is by using an OnPageChangeListener in conjunction with overriding the getItemPosition method in your adapter. Something like this:
In your custom pager adapter:
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
Then in your Activity containing the ViewPager:
final MyPagerAdapter adapter = new MyPagerAdapter();
pager.setAdapter(adapter);
pager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
... anything you may need to do to handle pager state ...
adapter.notifyDataSetChanged(); //this line will force all pages to be loaded fresh when changing between fragments
}
}
This should have the same effect as setting the OffscreenPageLimit to 0. However, this functionality is sort of against what the ViewPager is designed to provide. If you are trying to implement a ViewPager in this way, it may be worth reevaluating your layout to be sure that a ViewPager is really what you want to use.
UPDATE: There is a better way, Please have a look here: Update ViewPager dynamically?
Removing content of this answer because it was a really bad way to access Fragments inside ViewPager, please see the above link for better approach.
It's because you can specify the number of fragment your viewpager will "keep in RAM" (setOffScreenPageLimit () :I think the default is 1). So your second fragment is not reloaded. And when you go to a tab 3 or 4, then your 2 firsts fragments are deleted, then recreated when you come back.
To refresh your fragment, there is many way to do it: you could implement a listener interface of your own in it to tell it when to refresh, or simply call a method that you would implement to update your contents.
first override in the fragment method
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser){
actionView();//call the method to be refreshed
}
else{
//no
}
}
In my experience, ViewPagers keep:
Your current tab
& 1 tab either side in memory
So when you switch between 1 and 2, nothing is happening under the hood - it's like they are simply being hidden / shown.
But when you navigate to tab 4, tabs 1 & 2 are destroyed (onDestroy() called), so when you navigate back to either of them, they are being re-created fresh (onCreate() called).
As psykhi suggests, you could setOffScreenPageLimit() to 0, so that each fragment is created every time you navigate to it.
If you were interested in keeping the other pages in memory for performance purposes, as they were designed with this is mind, you could use a messaging/event publishing system to send a message from tab 1 to tab 2 telling it to update when you submit the form on tab 1.
Ok may be a late reply,might help others in future, so recently I faced same issue while fetching data from db into tabs it used to display only once I click on 3rd tab.. I tried above mentioned solution but nothing really worked.. fianlly i came across Otto
This library allows you to communicate within your app..
Just use this library to yourAdapter.notifyDataSetChanged() wherever you fetching your data from db..
This video helped me alot https://www.youtube.com/watch?v=lVqBmGK3VuA
I am trying to write a file manager for android as an university project. My idea is to use fragments in it to create a tabbed vew for it with a viewpager. Practically, the first view you get when you load the app is a path selection fragment, where you have sdcard, music, etc. That fragment contains a listview of said folders. When I click on an item, i want another fragment to be added to the pager, and that works fine. But I also want the current fragment to be replaced with a grid view of the "clicked" folder. That doesn't work. I use an arraylist of File[] to store files in each folder i selected. I also added methods inside the fragment adapter to add and replace fragments in my fragment arraylist. Tried with transaction and with my list, the list is correctly updated but the view is not. The following code is the method inside my fragment adapter to replace a fragment. It is called inside an onItemclicklistenr set to my listview:
public void replaceFragment(SherlockFragment newFragment, int pos) {
SherlockFragment old=fragments.get(pos);
fragments.set(pos, newFragment);
notifyDataSetChanged();
}
I read several answers about this issue, tried almost everything, nothing seems to work. Any help would be apreciated.
I found an horrible solution. After updating the fragment list (without fragmenttransaction) I re-set the adapter for the view pager and restore the current Item. This way I force the fragments layouts to be built. Not beautiful, but whatever. This is the interface that gets called onclick:
#Override
public void onPathSelected(int position) {
// TODO Auto-generated method stub
Log.d("positioninsideintfc", Integer.toString(position));
firstrun.add(true);
int current=mPager.getCurrentItem();
root=(File) filepaths.get(position);
fragmentfilelists.add(root.listFiles());
initpath=MainActivity.root.getAbsolutePath();
Gridfragment newfrag=Gridfragment.newInstance(current);
mAdapter.addFragment(Selectpathfragment.newInstance(current+1));
mAdapter.replaceFragment(newfrag, current);
mPager.setAdapter(mAdapter);
mPager.setCurrentItem(current);
}
You should learn your Activity to speak the language your frament use. In general your Activity should implement Interface which then your fragments will use for communication. So in your case, when user tap the list item, the fragment with listview shall tell its parent Actity (using said interface) something like "user clicked on this". And your parent activity should do the rest, replacing/adding fragments. Your fragment, like in OOP, shall be not aware of existence of other fragments.
I am using a ViewPager plugged to a FragmentStatePagerAdapter. Each fragment contains a listview. In this listview I wish to display ads and make calls to a analytics server. I only want to make those calls when the user navigates to a fragment (so I cannot use the onCreateView or onActivityCreated events in the Fragment). Is there an event I can hook on to this end ?
Update
I realized that the way I am fetching my current fragment is flawe
#Override
public void onPageSelected(int page) {
this.currentPage = page;
tabsAdapter.getItem(page);
}
getItem(int position) in the pager is actually responsible for instanciating the fragments, so it would create a new unattached fragment (hence getActivity() returning null). I think I will us an approach where I keep a map of the fragments (<Position, Fragment>), i will remove the fragment from the map when destoryItem is called in the pagerAdapter.
You are right, best option is the second solution here:
http://tamsler.blogspot.nl/2011/11/android-viewpager-and-fragments-part-ii.html
so your thoughts are correct.
How about this:
http://developer.android.com/reference/android/support/v4/view/ViewPager.OnPageChangeListener.html
The onPageSelected(int) method sounds like it does what you want.
I'm getting this on some cases, in onResume(), of an activity which uses a FragmentStatePagerAdapter. When using device's back button. Not always. Not reproducible.
I'm using support package v4, last revision (8).
Already searched with google, no success finding a useful answer.
Looking in the source, it's thrown here: FragmentManager.java
#Override
public void putFragment(Bundle bundle, String key, Fragment fragment) {
if (fragment.mIndex < 0) {
throw new IllegalStateException("Fragment " + fragment
+ " is not currently in the FragmentManager");
}
bundle.putInt(key, fragment.mIndex);
}
But why is the index of fragment < 0 there?
The code instantiating the fragments:
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
switch(position) {
case 0:
fragment = MyFragment.newInstance(param1);
break;
case 1:
fragment = MyFragment2.newInstance(param2, param3);
break;
}
return fragment;
}
#Override
public int getCount() {
return 2;
}
If your ViewPager is layouted inside a fragment (not an activty) :
mViewPager.setAdapter(new MyFragmentStatePagerAdapter(getChildFragmentManager()));
I had the same error here, but for another reason.
In my case I had override getItemPosition in my FragmentStatePagerAdapter. My ideia was to return the position, if the item exists, or a POSITION_NONE, if it doesn't exists anymore.
Well, my problem was the fact that when my collection got empty I returned POSITION_NONE. And that broke everything.
My fix was to return POSITION_UNCHANGED when I had an empty collection.
Hope it helps someone else.
The two key things to understand the bug are:
It happens sometimes.
It happens in onResume().
Given this information, it's likely that the ViewPager is not retaining the state of your Fragments. If you are manipulating the Fragments directly from the Activity, it could be the case that the off-page Fragment is getting destroyed and your Activity is trying to manipulate a null fragment. To retain the Fragment's state even when it is not in the current screen, the fix is pretty simple:
private static final int NUM_ITEMS = 2;
ViewPager mPager = /** instantiate viewpager **/;
mPager.setOffscreenPageLimit(NUM_ITEMS-1);
You can read about it here:
ViewPager Fragments getting destroyed over time?
Got it, the reason was, that I'm intantiating the Adapter each time in onResume().
If I instantiate the adapter only once, in the life cycle of the activity, this does not happen anymore.
This exceptions means that you are trying to attach a fragment to an activity which is no longer correct state to attach the fragments. What it means is, whenever we try to attach fragments (especially through an asynchronous call), there is a small probability that someone has pressed the back button and activity is in merge of getting destroyed while you are attaching the fragment. This is not always reproducible as its just a race condition, and might not always occur..
There are two ways to fix this:
1) This happens when you the onSaveInstanceState of your activity has been called and post to that you are trying to attach the fragment, since android wont be able to save the state of your fragment now, it will throw an exception. To overcome this and if you are not saving the state of your fragment, try using
commitAllowingStateLoss(), while committing the transaction.
2) To be very safe, check whether your activity is in correct state or not before attaching the fragment, use the following code in onPause:
boolean isInCorrectState;
public void onCreate{
super.onCreate();
isInCorrectState = true;
}
public void onPause() {
super.onPause();
if(isFinishing()){
isInCorrectState = false;
}
}
Now use this flag to check if your activity is in correct state or not before attaching the fragment.. Meaning attach the fragment iff isInCorrectState == true.
For me the reason was something else.
In my Loader.onLoaderReset() I cleared the data from the adapter. When I was leaving the app, onDestroy() caused the loader to reset, which cleared the FragmentStatePagerAdapter. I think it caused the adapter to clear all references to it's Fragments, but somehow, the FragmentManager didn't notice and threw the Exception. Doesn't seem very logical to me.
Note that for me it happened Activity.onDestroy().
Hope it helps someone.
Fragments in the ViewPager are fixed, instead of trying to replace the fragments in the adapter, try to give a different set of fragments and notifyDataSet changed, or take the advantage of FrameLayout to show another fragment over the view pager tab's current fragment.
There is my solution that works:
Swipe Gesture applied at Fragment level along with ViewPager with it's default swipe disabled