I have an app with a ViewPager which contains two fragments: a main fragment and a list fragment. If something in the list is selected, an event is raised which is handled in the activity. The activity switches to the main fragment and calls the "setSelectedItem" method. In this method,
I use getView() to find a specific view to change the text according to the selected item.
Most of the time it works, but sometimes getView returns null and I don't know why.
The pagerAdapter is created in OnCreate of the activity:
this.mPagerAdapter = new PagerAdapter(super.getSupportFragmentManager());
ViewPager pager = (ViewPager)super.findViewById(R.id.viewpager);
pager.setAdapter(this.mPagerAdapter);
The pager adapter creates instances of both fragments in its constructor:
public PagerAdapter(FragmentManager fm) {
super(fm);
this.main = MainFragment.newInstance();
this.list = ListFragment.newInstance();
}
This is the method in the activity which is called when an item from the list is selected:
ViewPager pager = (ViewPager)super.findViewById(R.id.viewpager);
pager.setCurrentItem(0, true);
((MainFragment)mPagerAdapter.getItem(0)).setSelectedItem(id);
And finally this is the line in the setSelectedItem method in the MainFragment which causes the problem:
TextView s = (TextView)getView().findViewById(R.id.CurrentSelection);
I tried several things I read about but the problem still occurs irregularly.
To start, I am not 100% sure, but I have a couple of points I need to mention. First, I don't like how you are using this and super like that, they seem to be vague, I believe a better way is to be more accurate considering pre-compile you are aware of their values. I think if you change your code to as following, it would help (just a hunch).
// No need for 'this' and 'super' here, you are within the activity
mPagerAdapter = new PagerAdapter(getSupportFragmentManager());
ViewPager pager = (ViewPager)findViewById(R.id.viewpager);
pager.setAdapter(mPagerAdapter);
public PagerAdapter(FragmentManager fm) {
super(fm);
// No need for 'this' here, you are within the class
main = MainFragment.newInstance();
list = ListFragment.newInstance();
}
// No need for 'super' here, you are within the activity
ViewPager pager = (ViewPager)findViewById(R.id.viewpager);
pager.setCurrentItem(0, true);
((MainFragment)mPagerAdapter.getItem(0)).setSelectedItem(id);
// Here use 'getActivity()' instead of 'getView()'
TextView s = (TextView)getActivity().findViewById(R.id.CurrentSelection);
Let me know what happens, cheers.
Related
This is a little complicated to explain. In my layout I have a ViewPager component. In my MainActivity I initialize it and set an custom FragmentStatePagerAdapter on it:
mPager = (ViewPager) findViewById(R.id.pagerMain);
mPagerAdapter = new ScreenSlidePagerAdapter(this.getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
This is my Custom ScreenSlidePagerAdapter:
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
public ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return ScreenSlidePageFragment.init(position, candidateID);
}
#Override
public int getCount() {
return NUM_PAGES;
}
}
The ScreenSlidePageFragment is initialized with some ImageViews. So far so good. What happens next is, I update the the content, meaning the ImageViews in the PageViewer and use
mPager.setAdapter(mPagerAdapter);
to update. I can see the new ImageViews on the screen, as I reset the adapter. Exactly this sudden change is what I want to avoid. When the ViewPager refreshes its content, I want this to be animated. A little like on tinder, when you dismiss a person. Again: I'n not talking about the swipe animation between the ImageViews after the adapter has been set, but as I change/rebuild the adapter itself.
I tried many things, but I just dont know where to apply the animation and to which object. Do you have an idea, how refreshing the adapter can be animated in a way like fragments are animated when they are replaced?
After a lot of try&error I solved it by adding a "RootFragment" for the first ItemPosition, replacing this Fragment with my real first Fragment and as I replace using the common FragmentTransaction setting the setCustomAnimations to it.
I think there are better ways of doing this, in most cases when the PageViewer is set, it stays like that. Thats why I had so much difficulties solving this issue. If you want to read more about PageViewer you should read this:
https://guides.codepath.com/android/ViewPager-with-FragmentPagerAdapter
I have a ViewPager, defined in an Activity, and many Fragments sequentially shown in the ViewPager. In these fragments there are dynamically constructed checkboxes and radiobuttons, which the user is supposed to manipulate. On the very moment that the user swipes to the next page I need the user data to be retrieved and stored in the Application object. I can't figure out what the standard way of doing this is. Since there are many Fragments I opted for using the FragmentStatePagerAdapter. Any help would be welcome, thanks in advance!
Update-1:
I do have this:
pageAdapter = new MyPageAdapter(getSupportFragmentManager());
pager = (ViewPager) findViewById(R.id.viewpager);
pager.setAdapter(pageAdapter);
// detects viewpager page change
pager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
Log.i("TAG", "onPageSelected");
int index = pager.getCurrentItem();
MyPageAdapter adapter = ((MyPageAdapter) pager.getAdapter());
QuestionFragment fragment = (QuestionFragment) adapter.getItem(position);
if (fragment.rdbtn != null) {
for (int i = 0; i < fragment.rdbtn.length; i++) {
if (fragment.rdbtn[i].isChecked())
Log.i("TAG", "checked");
else
Log.i("TAG", "not checked");
}
}
// fragment.refresh();
}
});
When checking the debugger, after starting up, the ViewPager instantiates Fragments 0 and 1 (standard behavior). When the user manipulates fragment-0 and swipes, the handler is indeed called but with position=1, not 0. And the public elements I want to read are null!
UPDATE-2
I notice in the debugger that the data I need is stored in adapter.mCurrentPrimaryItem.
How to retrieve CurrentPrimaryItem in the code?!
You can implement a pageChangeListener in your activity and set this to the viewPager.
Then you can have a class abstract BaseFragment extends Fragment and declare an abstract method, say, getData() that every fragment in the ViewPager extends and overrides the method.
And in onPageSelected() of the activity you can access those data.
I have a Fragment (I'll call it pagerFragment) that is added to the backstack and is visible. It holds a viewPager with a FragmentPagerAdapter. The FragmentPagerAdapter holds (let's say) two fragments: A and B.
First adding of the fragments works great.
Fragment A has a button that once clicked, adds a fragment (C) to the backstack.
The problem is this: if I add that fragment (C), and then click back, the pagerAdapter is empty, and I cannot see any fragments inside.
If I use a hack, and destroy the children fragments (A and B) in the pagerFragments onDestroyView(), this solves the problem, although I don't wan't to use this hack.
Any ideas what the issue could be?
I had the same problem. The solution for me was simple:
in onCreateView I had:
// Create the adapter that will return a fragment for each of the three
// primary sections of the app.
mSectionsPagerAdapter = new SectionsPagerAdapter(getActivity()
.getSupportFragmentManager());
where SectionPageAdapter is something like this:
class SectionsPagerAdapter extends FragmentPagerAdapter {
...
}
after changing getSupportFragmentManager to
mSectionsPagerAdapter = new SectionsPagerAdapter(getChildFragmentManager());
it started working!
It sounds like you are using nested fragments since your ViewPager is inside a PagerFragment. Have you passed getChildFragmentManager() to the constructor of your FragmentPagerAdapter? If not you should.
I don't think you need a FragmentStatePagerAdapter, but I would give that a shot since it handles saving and restoring Fragment state. The fact that your onDestroyView() hack works makes me think that you may want a FragmentStatePagerAdapter.
It could also have something to do with the way the FragmentPagerAdapter adds Fragments. The FragmentPagerAdapter doesn't add Fragments to the backstack. Imagine if you had a 10+ pages added in your ViewPager and the user swiped through them. The user would need to hit back 11 times just to back out of the app.
It may also be related to this post: Nested Fragments and The Back Stack.
Also I'm not sure what you are adding the Fragment C to. Are you adding it to the same container as the ViewPager?
Well at least you have a few options to investigate. In these situations I like to debug down into the Android SDK source code and see what's causing the behaviour. I recommend grabbing the AOSP source and adding frameworks/support and frameworks/base as your SDK sources. That's the only true way to understand what is happening and avoid making random changes until things work.
Use getChildFragmentManager() instead of getSupportFragmentManager().
It will work fine.
I just faced the problem in our project as well. The root cause is the way the the FragmentPagerAdapter works:
The FragmentPagerAdapter just detaches a Fragment he does not currently need from its View but does not remove it from its FragmentManager. When he wants to display the Fragment again he looks if the FragmentManager still contains the Fragment using a tag that is created from the view id of the ViewPager and the id returned by the adapters getItemId(position) call. If he finds a Fragment he just schedules an attach of the Fragment to its View within the updating transaction of the FragmentManager. Only if he does not find a Fragment this way he creates a new one using the adapters getItem(position) call!
The problem with a Fragment containing a ViewPager with a FragmentPagerAdapter is, that the contents of the FragmentManager is never cleaned up when the containing Fragment is put to the back stack. If the containing Fragment comes back from the back stack it creates a new View but the FragmentManager still contains the fragments that were attached to the old view and the attach of an existing fragment does not work anymore.
The easiest way to get rid of this problem is to avoid nested fragments. :)
The second easiest way is as already mentioned in other posts to use the ChildFragmentManager for the FragmentPagerAdapter as this one gets properly updated during the life cycle of the container fragment.
As there are projects (as my current one) where both options are not possible, I have published here a solution that works with an arbitrary FragmentManager by using the hashCode of the sub fragments as the item id of the fragment at that position. It comes at the price of storing all fragments for all positions within the adapter.
public class MyPagerAdapter extends FragmentPagerAdapter {
private static int COUNT = ...;
private final FragmentManager fragmentManager;
private Fragment[] subFragments = new Fragment[COUNT];
private FragmentTransaction cleanupTransaction;
public MyPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
this.fragmentManager = fragmentManager;
}
#Override
public Fragment getItem(int position) {
return getSubFragmentAtPosition(position);
}
#Override
public int getCount() {
return COUNT;
}
#Override
public long getItemId(int position) {
return getSubFragmentAtPosition(position).hashCode();
}
//The next three methods are needed to remove fragments no longer used from the fragment manager
#Override
public void startUpdate(ViewGroup container) {
super.startUpdate(container);
cleanupTransaction = fragmentManager.beginTransaction();
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
cleanupTransaction.remove((Fragment) object);
}
#Override
public void finishUpdate(ViewGroup container) {
super.finishUpdate(container);
cleanupTransaction.commit();
}
private Fragment getSubFragmentAtPosition(int position){
if (subFragments[position] == null){
subFragments[position] = ...;
}
return subFragments[position];
}
}
I had same problem, just set adapter twice at once and that's all.
Example code :
private fun displayImg(photo1:String, photo2:String){
val pager:ViewPager = v?.findViewById(R.id.ProductImgPager)!!
val arr = ArrayList<String>()
arr.add(photo1)
arr.add(photo2)
pager.adapter = AdapterImageView(fm, arr ,arr.size)
pager.adapter = AdapterImageView(fm, arr ,arr.size)
}
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.
Currently, with a FragmentActivity, I toggle among 2 type of Fragments using the following code.
private void toggle() {
Fragment oldFragment = getSupportFragmentManager().findFragmentById(R.id.content);
Fragment fragment = null;
if (oldFragment instanceof ColorFragment) {
fragment = new ViewPagerFragment();
} else {
fragment = new ColorFragment(android.R.color.black);
}
getSupportFragmentManager().beginTransaction().replace(R.id.content, fragment).commitAllowingStateLoss();
}
2 Fragments are being toggle.
ColorFragment - A simple fragment which fill up its background with solid black color.
ViewPagerFragment - A fragment contains ViewPager. User can swipe between a purple color fragment, and a blue color fragment.
The code which responsible for swiping purple and blue color fragments are as below.
private static class MyFragmentPagerAdapter extends FragmentPagerAdapter {
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return 2;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new ColorFragment(android.R.color.holo_purple);
default:
return new ColorFragment(android.R.color.holo_blue_bright);
}
}
}
However, I encounter the weird behavior during toggling.
Black color fragment was shown.
Toggling.
View pager, which can swipe between purple and blue fragments shown.
Toggling.
Black color fragment was shown.
Toggling.
Nothing shown, as MyFragmentPagerAdapter's getItem is not being triggered.
I think my situation is similar to FragmentPagerAdapter getItem is not called
However, I prefer not to use FragmentStatePagerAdapter, because of the cost of potentially more overhead when switching between pages.
Any workaround to overcome this problem?
I include a complete workable source code to demonstrate this problem : https://www.dropbox.com/s/jok9tz5ktvfcteo/viewpagerbug.zip
Any workaround to overcome this problem?
I've downloaded your code and the problem appears because you don't handle those Fragments right. Most precisely you use nested Fragments in the ViewPager based Fragment and for that ViewPager you create the adapter like this:
MyFragmentPagerAdapter myFragmentPagerAdapter = new MyFragmentPagerAdapter(this.getFragmentManager());
Instead, you should be using getChildFragmentManager() to bind the nested fragments:
MyFragmentPagerAdapter myFragmentPagerAdapter = new MyFragmentPagerAdapter(this.getChildFragmentManager());
Also, you shouldn't pass data through a constructor to a Fragment as that data will not survive a configuration change and bad things will start to appear. Use a Bundle instead.
Global working tested solution.
getSupportFragmentManager() keeps the null reference some times and View pager does not create new fragment instance.Since it finds reference to same fragment. So to over come this use getChildFragmentManager() solves problem in simple way.
Don't
new PagerAdapter(getSupportFragmentManager(), fragments);
Do
new PagerAdapter(getChildFragmentManager() , fragments);
Simple use FragmentStatePagerAdapter instead of FragmentPagerAdapter
or
you can use new MyFragmentPagerAdapter(this.getChildFragmentManager())
Hope it will help you :)
In my case I was correctly calling
MyFragmentPagerAdapter myFragmentPagerAdapter = new MyFragmentPagerAdapter(this.getChildFragmentManager());
but then in the nested fragment I was trying to replace the container fragment with another one by using:
getFragmentManager()
You need to go to the activity and call
getActivity().getSupportFragmentManager();
In my cases it worked after add this to my FragmentPagerAdapter:
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
and I also used getChildFragmentManager() like Luksprog said