My app is a news app, with news listing fragment, NewsDetailsFragment (which contains menu, search, etc). I am using the single activity model and each screen is represented by a Fragment. NewsDetailsFragment includes a ViewPager (gallery).
When I navigate to NewsDetailsFragment initially, the gallery is loaded, now if I load a new instance of NewsDetailsFragment fragment, to choose another news from the menu, doing a search, etc. The ViewPager in the 2nd NewsDetailsFragment won't display the images, although the Log shows that everything works fine, data is there, adapter calls getView()...
So basically if there is 1 loaded gallery ViewPager, any more NewsDetailsFragment won't show their galleries, only if I press Back till I have no fragment (hence no ViewPager running), only then if I open a new instance of NewsDetailsFragment it will load the gallery.
I hope I explained clearly. I tried my best and I'm clueless now.
This is how I pass the gallery data to the ViewPager after it is loaded (code excerpt from NewsDetailsFragment):
ViewPager pager = (ViewPager) getView().findViewById(R.id.vpAlbum);
ArticleAlbumAdapter adapter = new ArticleAlbumAdapter(getActivity().getSupportFragmentManager(), article.album);
pager.setAdapter(adapter);
ArticleAlbumAdapter:
public class ArticleAlbumAdapter extends FragmentStatePagerAdapter
{
private List<String> urlList;
public ArticleAlbumAdapter(FragmentManager fm, List<String> urlList)
{
super(fm);
this.urlList = urlList;
Log.d("ArticleAlbumAdapter()", "urlList=" + urlList);
}
#Override
public Fragment getItem(int position)
{
Log.d("ArticleAlbumAdapter.getItem()", "position=" + position + ", url=" + urlList.get(position));
Fragment image = new AlbumImageFragment();
Bundle bundle = new Bundle();
bundle.putString("url", urlList.get(position));
bundle.putInt("pos", position);
image.setArguments(bundle);
return image;
}
#Override
public int getCount()
{
return urlList.size();
}
}
Pass the ViewPager your current Fragment's getChildFragmentManager() rather than the FragmentManager of the activity. Based on your description, I'm assuming your main screen which creates the first ViewPager is also a Fragment. Since the ViewPager is a child to your Fragment and it in turn is creating child Fragments, it needs the child FragmentManager rather than the main one.
Related
There is 3 TabLayout with ViewPager in my application.
If I swipe for change Tabs then it behaves strangely.
Below is the scenario.
Let's say 3 Tabs A, B, C.
Default Tab A will open when I start App.
Go to B From A - Works OK.
Go to A From B - Works Ok.
Go To B From A - Works Ok.
Go To C From B - Works Ok.
Now Problem Starts here.
Go To B From C - It will Load A and B Both but display Only B.
Why A and B loads if I come from C ??
Below is my code.
private Toolbar toolbar;
private TabLayout tabLayout;
private ViewPager viewPager;
ViewPagerAdapter adapter;
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
viewPager = (ViewPager) findViewById(R.id.viewpager);
setupViewPager(viewPager);
tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(viewPager);
private void setupViewPager(ViewPager viewPager) {
adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.addFragment(new FragmentOpen(), "OPEN");
adapter.addFragment(new FragmentClose(), "CLOSE");
adapter.addFragment(new FragmentTest(), "TEST");
viewPager.setAdapter(adapter);
}
class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
#Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
I want to load Tabs every time.
For that, I use below code.
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
if(adapter != null){
adapter.getItem(position).onResume();
}
Toast.makeText(getApplicationContext(), "Page Selected", Toast.LENGTH_LONG).show();
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
But it will always load both tabs when I came from C.
What I want is: only Tab B must load when came from C, not Both.
Its not possible when using ViewPager. ViewPager loads at least one right/left adjacent fragment of current fragment as per your swipe direction.
If you read the ViewPager documentation, there is a method ViewPager.setOffscreenPageLimit(LIMIT) which supports off screen page limit and its default value is 1.
SetOffscreenPageLimit:
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.
You should keep this limit low, especially if your pages have complex
layouts. This setting defaults to 1.
See documentation.
Here are some related answers:
1. ViewPager.setOffscreenPageLimit(0) doesn't work as expected
2. How can make my ViewPager load only one page at a time ie setOffscreenPageLimit(0);
3. How does setOffscreenPageLimit() improve ViewPager performance by retaining more offscreen Fragments?
its Default behavior of ViewPager if you are in A then it loads B
also and when your reach B it will Load C and A also is Loaded now
when to reach at C its destroy the View of A and now when you come
again to B it will Load A again, in a simple way ViewPager Maintain
the One Next and One previous state of View by Default.
to Overcome this you should use ViewPager.setOffscreenPageLimit(number) here number is the count of page to load Simulationally for Next and Previous View.
I had only 3 tabs and settingViewPager.setOffscreenPageLimit(2) worked for me.
Try ViewPager.setOffscreenPageLimit(totTabs - 1)
My MainActivity contains a viewPager.
In the MainActivity.java, I set the adapter for viewpager. The adapter extends FragmentStatePagerAdapter. The fragment I want to replace is a
cameraFragment. So when the user clicks on the switch camera button, I want to now show the camera fragment, this time with a front camera on.
On clicking the switch Camera button, I remove the fragment from the arraylist of fragments I had passed to the custom adapter. I add the new fragment and call notifydatasetchanged. However, this does not result in the new fragment being added. How do I achieve dynamic replacement of fragments within a viewpager which is backed my a custom fragment state pager adapter?
Code :
mainPageFragments = new ArrayList<>();
mainPageFragments.add(new ResultsFragment_());
mainPageFragments.add(DemoCameraFragment_.newInstance(false));
pagerAdapter = new MainViewPagerAdapter(getSupportFragmentManager(),mainPageFragments);
To replace the fragment : On receiving the related event I do,
mainPageFragments.remove(1);
if (event.getCameraState().equals(CameraSwitchButton.CameraTypeEnum.BACK)) {
mainPageFragments.add(DemoCameraFragment.newInstance(false));
} else {
mainPageFragments.add(DemoCameraFragment.newInstance(true));
}
// Not Working...
pagerAdapter.notifyDataSetChanged();
Adapter Code :
public class MainViewPagerAdapter extends FragmentStatePagerAdapter {
ArrayList<Fragment> fragmentsArray;
public MainViewPagerAdapter(FragmentManager fm, ArrayList<Fragment> fragmentsArray) {
super(fm);
this.fragmentsArray = fragmentsArray;
}
#Override
public Fragment getItem(int position) {
return fragmentsArray.get(position);
}
#Override
public int getCount() {
return fragmentsArray.size();
}
#Override
public int getItemPosition(Object object) {
return super.getItemPosition(object);
}
}
Your MainViewPagerAdapter.getItemPosition is the cause of your issue.
Default implementation always returns POSITION_UNCHANGED. For pager to remove your fragment you have to return PagerAdapter.POSITION_NONE for the fragments that are removed.
Additionally your current design contradicts with the idea of FragmentStatePagerAdapter. From the FragmentStatePagerAdapter documentation: "This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages."
Your current implementation holds all fragments in an array, and so defeats this mechanism. Correct implementation would be to create fragments in MainViewPagerAdapter.getItem method and let the adapter to handle fragments lifecycles as needed.
Thanks to #Okas. I made the following change to getItemPosition within my FragmentStatePagerAdapter subclass.
#Override
public int getItemPosition(Object object) {
if (object instanceof DemoCameraFragment_)
return POSITION_NONE;
return super.getItemPosition(object);
}
I added logs to the OnCreate of both my fragments to confirm if they were getting recreated or not. As per my requirement, only the second fragment is recreated.
I want to show a same fragment for example fragment having activities of a day of week in a viewpager for all days with different data. I will be giving the dayNumber parameter to each fragment being instantiated and showing related activities. The problem is I see same fragment in each tab no matter what parameter I passed. I think the last fragment added or instantiated by a pager overrides all the other tab fragments instance. Because when I open a list item in expendableList View it is opened in all fragments of the pager.
This is how I am using the pager and fragment.
Pager
mPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
// When swiping between pages, select the
// corresponding tab.
if (bar.getNavigationMode() == ActionBar.NAVIGATION_MODE_TABS)
if (bar.getSelectedNavigationIndex() != position)
bar.setSelectedNavigationItem(position);
// should be changed when some solution comes.
if (tab == 0) {
Fragment ev;
if ((ev = (Fragment) mPagerAdapter.instantiateItem(mPager,
0)) instanceof frTimetable)
((frTimetable) ev).refresh(day.Monday);
} else if (tab == 1) {
Fragment ac;
if ((ac = (Fragment) mPagerAdapter.instantiateItem(mPager,
1)) instanceof frTimetable)
((frTimetable) ac).refresh(day.Tuesday);
}
tab = position;
}
Fragement
{
//class other methods
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fr_timetable, container, false);
ExpandList = (ExpandableListView) view.findViewById(R.id.expActivityView);
//I will change the list items in refresh method of the fragment for a day type
ExpListItems = new ArrayList<Items>(Timetable_Provider.getAllActivites());
ExpAdapter = new ExpandListAdapter(getActivity(), ExpListItems);
ExpandList.setAdapter(ExpAdapter);
return view;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
instantiateItem() is certainly not what you want. That is called by ViewPager, not by a consumer of a ViewPager.
Normally, you would provide the data to the ViewPager as part of setting up the pages, inside of your PagerAdapter. For example, this sample app uses the arguments Bundle to pass the page number of the page to the Fragment that is the implementation of the page.
If the data inside a page needs to be updated, ideally the page itself determines on its own that this is needed and handles it. Or, use an event bus (e.g., LocalBroadcastManager, greenrobot's EventBus, Square's Otto) to publish information that relevant pages can pick up. There is no great way to get at an existing page from outside of the page itself using FragmentPagerAdapter or FragmentStatePagerAdapter (e.g., to have an activity push data into a page), which is one of the reasons I wrote ArrayPagerAdapter.
I have a FragmentPagerAdapter for a viewPager Which initially has only one Fragment in it. I want to dynamically add a new Fragment to the adapter when user swipes from right to left, and dynamically remove a Fragment when user swipes from left to right.I have tried to use this library https://github.com/commonsguy/cwac-pager but in that library we have an option to add and remove fragments on button clicks. I have tried to add a OnPageChangeListener to the viewpager but the callback methods ( onPageScrolled and onPageScrollStateChanged) are being called more than once which results in addition of more than one fragment to the FragmentPagerAdapter. So please shed some light on how to do this.
#dora: i think in your case FragmentStatePagerAdapter will help you. I have mentioned its use below as per my understanding.I hope it will help you in taking decision.
There are two ways to implement ViewPager:
• FragmentStatePagerAdapter
• FragmentPagerAdapter
FragmentStatePagerAdapter class consumes less memory, because it destroys fragments, as soon as they are not visible to user, keeping only saved state of that fragment
FragmentPagerAdapter: when there are less number of fragments. But using AndroidFragmentPagerAdapter for large number of fragments would result choppy and laggy UX.
Number of page hold by a viewPager?
The number of items that any ViewPager will keep hold of is set by the setOffscreenPageLimit() method. The default value for the offscreen page limit is 3. This means ViewPager will track the currently visible page, one to the left, and one to the right. The number of tracked pages is always centered around the currently visible page.
Please follow this link for code: http://www.truiton.com/2013/05/android-fragmentpageradapter-example/
I know this post is old, but I struggled to figure this out so I'll answer it anyway.
You want to use FragmentStatePagerAdapter and override getItemPosition(). Create a list of stuff you want to pass down to the fragment, call notifyDataSetChanged(), and you're all set!
Here's the adapter:
public class SectionsPagerAdapter extends FragmentStatePagerAdapter {
List<String> mKeyList = new ArrayList<>();
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
#Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
// Return a PlaceholderFragment (defined as a static inner class below).
return PlaceholderFragment.newInstance(mKeyList.get(position));
}
#Override
public int getCount() {
return mKeyList.size();
}
#Override
public CharSequence getPageTitle(int position) {
return "SCOUT " + (getCount() - position);
}
public void add(int position, String key) {
mKeyList.add(position, key);
notifyDataSetChanged();
}
}
And here's the fragment:
public static class PlaceholderFragment extends Fragment {
private static final String ARG_SCOUT_KEY = "scout_key";
public static PlaceholderFragment newInstance(String key) {
PlaceholderFragment fragment = new PlaceholderFragment();
Bundle args = new Bundle();
args.putString(ARG_SCOUT_KEY, key);
fragment.setArguments(args);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.current_scout_fragment, container, false);
//getArguments().getString(ARG_SCOUT_KEY));
return rootView;
}
}
I want to dynamically add a new Fragment to the adapter when user swipes from right to left, and remove dynamically remove a Fragment when user swipes from left to right.
AFAIK, that will not be supported by any PagerAdadpter. It certainly will not be supported by ArrayPagerAdapter. The page needs to exist, otherwise you cannot swipe to it. You cannot swipe first, then add the page later.
Moreover, I have never found a use case for your proposed pattern that could not be handled by having the page be in the adapter, but not populating the page (i.e., whatever the expensive work is that you appear to be trying to avoid) until the swipe begins.
I am having a hard time figuring out the next thing.
What I have: I have a viewpager and several pages in it. In this question only two of them is important, lets call them Fragment1 and Fragment2 and they are next to each other. Fragment1 contains a listview which is filled with data from the internet (external database). Fragment2 contains a simple button.
My goal: If I click on the button in Fragment2, I add a new item to the external database. I would like to update/refresh the listview in the Fragment1 with this new item.
The notifyDataChanged() doesnt work in my case, however so far I was convinced that it reinstantiates every pages.. I am going to introduce my problem the clearest way I can, so lets see the code I have, this is my ViewPager adapter:
class MyPagerAdapter extends FragmentStatePagerAdapter {
public List<String> fragmentsA;
public MyPagerAdapter(FragmentManager fm) {
super(fm);
fragmentsA = fragments;
}
#Override
public Fragment getItem(int position) {
return Fragment.instantiate(context, fragmentsA.get(position));
}
#Override
public CharSequence getPageTitle(int position) {
return mEntries.get(position % CONTENT.length).toUpperCase();
}
#Override
public int getCount() {
return mEntries.size();
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
Fragment1 onCreateView() (shortly):
public View onCreateView(LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
getData();
View view = inflater.inflate(R.layout.latestapps_tab, container, false);
lw = (ListView) view.findViewById(R.id.lw);
context = getActivity().getApplicationContext();
act = this.getActivity();
m_adapter = new ItemAdapter();
lw.setAdapter(m_adapter);
return view;
}
I create the ViewPager and the adapter, I set the adapter for the ViewPager afterwards I fill the my viewpager with my fragments in my Main class. After this I am goint to have a fully functional ViewPager with 2 fragments.
pager = (ViewPager)findViewById( R.id.viewpager );
adapter = new MyPagerAdapter(getSupportFragmentManager());
indicator = (TabPageIndicator)findViewById( R.id.indicator );
pager.setAdapter( adapter );
indicator.setViewPager( pager );
pager.setCurrentItem(INITIAL_PAGE);
pager.setOffscreenPageLimit(3);
//adding fragments to the pager
fragments.add( Fragment1.class.getName());
fragments.add( Fragment2.class.getName());
In the Fragment1 I have a listview with some textviews in every list item. The loading works perfectly: I create the ArrayLists and I fill thoes lists with data from the external database. After loading is done, I fill the listviews with these tons of data.
In Fragment 2 I click on the button and I would like that listview to be updated so a new row should be created in the listview with some data from the external database. (of course writing into the database works)
My guess, that I might not refresh the ArrayLists or I dont reinstantiate the Fragment1, so the getDataFromSQL() method never turns only if I exit and launch the application again or I swipe so much in the ViewPager that the Fragment1 gets detached. So I cannot update or refresh the Fragment1. Could someone help in this questionL?
EDIT
I managed to make it happen with delivering a message to the fragment2 to update itself. But I am not sure if it is a good solution and there is not a better way, i.e. just refreshing somehow the whole fragment.
SOLUTION
Okay I think it must have been my laziness but I solved it now. For everyone who still wants to refresh a fragment from another one or just make conection between fragments, I tell you the appropriate approach:
You have to implement your own listener which helps you communicate between the fragments through the holder activity. This can be found here: http://developer.android.com/training/basics/fragments/communicating.html . Very simple and useful.
You have to retrieve the fragment, which is again simple: Retrieve a Fragment from a ViewPager These Q offers several acceptable way, I used the SpareArray solution.
Thank you for the help anyway!
you need be able to get your fragments from your activity, to do that you need to get the fragment from your adapter, you will need to add a couple methods to your page adapter
public Fragment getFragment(ViewPager container, int position, FragmentManager fm) {
String name = makeFragmentName(container.getId(), position);
return fm.findFragmentByTag(name);
}
private String makeFragmentName(int viewId, int index) {
return "android:switcher:" + viewId + ":" + index;
}
then from your activity make the following method
public Fragment getFragmentbyPosition(int position) {
return adapter.getFragment(pager, position, getSupportFragmentManager());
}
now on fragment2 call the following:
Fragment1 fragment1 = (Fragment1) ((MyActivity)getActivity()).getFragmentbyPosition(0);
now you will be able to call public methods on fragment1 from fragment 2, so just use that in your onClick and tell fragment1 to update it's listview.
now the reason makeFragmentName works is that is how the FragmentPagerAdapter creates the tag for the fragments it makes.