ViewPager creates duplicate views - android

This is the PagingAdapter I am using:
public class PagingAdapter extends FragmentStatePagerAdapter {
Context context;
public PagingAdapter(FragmentManager fm, Context con) {
super(fm);
context = con;
}
#Override
public Fragment getItem(int index) {
try {
switch (index) {
case 0:
return Fragment.instantiate(context, ActivityFragment.class.getName());
case 1:
return Fragment.instantiate(context, GroupFragment.class.getName());
case 2:
return Fragment.instantiate(context, MessageFragment.class.getName());
case 3:
return Fragment.instantiate(context, NotificationFragment.class.getName());
}
return null;
}
catch (Exception ex)
{
ex.printStackTrace();
return null;
}
}
#Override
public int getCount() {
// get item count - equal to number of tabs
return 4;
}
}
This creates same view for first two tabs. I want separate content for all tabs.
I am placing WebView in all 4 tabs.
In Activity, I am doing this :
mViewPager.setOffscreenPageLimit(3);

Use SparseArray<Fragment> in your adapter, and in your getItem use new instantiate your fragments like
return new ActivityFragment();
If you want to add bundle info than first create your fragment than add bundle in your
fragment.setArgument(bundle);
And use these line in your deateyItem before calling super
if(0<=sparseArray.indexOfKey(position))
sparseArray.remove(position);

First of all your method instantiate is returning a Fragment right?
Does it add the param implicitly in that method using setArguements if not you may want to change to it. On the other hand FragmentManager will call default constructor of each fragment when needed. So you have to keep them (do not overload constructor) and in destroyItem try removing the fragment for real.
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
getFragmentManager().beginTransaction().remove((Fragment)(object)).commit();
}

What do you mean by separate view? are you sure that you are not inflating the wrong layout in.
GroupClassFragment.java class
Do check int the method.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View myCorrectView = inflater.inflate(R.layout.my_correct_view,container,false);
return myCorrectView;
}

Related

Fragment View is Null When Called From Parent Activity

I have an activity and its child Fragment with a LinearLayout that I generate buttons inside of. When the fragment is created, everything runs fine. However, when the parent activity downloads a new item, I call the method in the fragment that is used to generate the buttons and add them to the view, but the LinearLayout returns null and I can't figure out why. I either need to fix it or find a way to "re-display" my fragment. Here is the related code:
SongFragment:
LinearLayout linearLayout;
DatabaseHelper databaseHelper;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_song, container, false);
linearLayout = rootView.findViewById(R.id.songFragmentMainLayout);
databaseHelper = new DatabaseHelper(getActivity());
return rootView;
}
#Override
public void onResume() {
super.onResume();
RefreshButtons();
}
public void RefreshButtons(){
linearLayout.removeAllViews(); //this line is where the NullPointerException is called
...
}
MainActivity:
//refresh fragment view
SongFragment fragment = (SongFragment) sectionsPagerAdapter.getItem(0);
if(downloadQueue.size() == 0){
fragment.RefreshButtons();
Toast.makeText(context, "New songs downloaded", Toast.LENGTH_SHORT).show();
}
...
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
switch (position) {
case 0:
fragment = new SongFragment();
break;
case 1:
fragment = new PlaceholderFragment();
break;
}
return fragment;
}
#Override
public int getCount() {
return 2;
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return "Songs";
case 1:
return "Playlists";
}
return null;
}
}
Thanks for your help.
The method fragment.RefreshButtons(); returns an NPE because if you implemented SectionsPagerAdapter like you should, getItem() returns a new Instance of that fragment which is not yet attached to the fragment manager, therefore causing a Nullpointer exception.
So what you should do is get a currently active fragment instance like this:
Fragment frag = (Fragment) mViewPager.getAdapter().instantiateItem(mViewPager, 0);
0 is the position of your fragment, so for example if you have 3 fragments, 0 will return the first fragment instance etc...

FragmentStatePagerAdapter is not calling getItem()

i have seen the relative post to my issue but, it's not solve my probleme.
I'm trying to get a swipe view with 3 fragment.
In the main fragment i have this:
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View vue = inflater.inflate(R.layout.fragment_conversions, container, false);
ConversionsPagerAdapter cPageAdapter = new ConversionsPagerAdapter(getActivity().getSupportFragmentManager());
ViewPager vp = vue.findViewById(R.id.conversionsPager);
cPageAdapter.getItem(0);
vp.setAdapter(cPageAdapter);
Button convKmhKts = (Button) vue.findViewById(R.id.convKmhKts);
convKmhKts.setText("...");
...
Here is the class of my FragmentStatePagerAdapter:
public class ConversionsPagerAdapter extends FragmentStatePagerAdapter{
public ConversionsPagerAdapter(FragmentManager fm) {
super(fm);
Log.i("ARKA", "FragmentStatePagerAdapter CONSTRUCTOR");
}
#Override
public Fragment getItem(int i) {
return ConversionFragment.newInstance(i);
}
#Override
public int getCount() {
return 1;
}
}
And finnaly the Fragment which display the Tab:
public class ConversionFragment extends Fragment {
public static final String ID_CONVERSION = "id_conversion";
public static ConversionFragment newInstance(int pos) {
ConversionFragment stf = new ConversionFragment();
Bundle args = new Bundle();
args.putInt("pos", pos);
stf.setArguments(args);
return stf;
}
public ConversionFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
// The last two arguments ensure LayoutParams are inflated
// properly.
Bundle args = getArguments();
int idConversion = args.getInt(ID_CONVERSION);
Log.i("idConversion", String.valueOf(idConversion));
int idVue;
switch (idConversion){
case 0: idVue = R.layout.fragment_conversions_vitesse;
break;
case 1: idVue = R.layout.fragment_conversions_vitesse;
default: idVue = R.layout.fragment_conversions_vitesse;
break;
}
View rootView = inflater.inflate(idVue, null);
return rootView;
}
}
The fact is, the getItem() is never called.
I try to change getActivity().getSupportFragmentManager() by getChildFragmentManager(), but it seem it's not the good way...
When i'm trying to acces my button i get this:
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.Button.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
At the line where i'm using the button.
If you use ViewPager inside fragment you have to use chield fragment manager so try replace getActivity().getSupportFragmentManager() with getChildFragmentManager(). If you use one fragment manager for all stuff, it will bring some bugs to you.
For more information
You are doing wrong here
cPageAdapter.getItem(0);
vp.setAdapter(cPageAdapter);
Set adapter to view pager first and then try to call get item method.
vp.setAdapter(cPageAdapter);
cPageAdapter.getItem(0);
Ok,
the probleme was that i try to acces a component which is not inflated:
convKmhKts.setText("...");
I acces to it in the child fragment and now it's ok....

Using setCurrentItem when slidetab is inside a fragment

My application has to swipe in between fragments when a button is pressed. If I was hosting swipe tab and view pager inside an activity, I would do something like this.
((ParentActivityName) getActivity()).setCurrentItem(2, true);
Now I have a parent Fragment that hosts the slide tabs. It has the following method to set current child Fragment to viewpager.
public void setCurrentItem (int item, boolean smoothScroll) {
pager.setCurrentItem(item, smoothScroll);
}
On Click of "Next" button in one of the sliding tab Fragments, I am trying to call the method as
new FragUserRegistration().setCurrentItem(1,true);
But is simply returning a null object reference error. Any help would be much appreciated.
I worked it out simply by calling viewpager from parent Fragment to each sliding tab fragments and then the associated setCurrentItem method.
viewPager = (ViewPager)getActivity().findViewById(R.id.pager);
viewPager.setCurrentItem(int position, true);
//four swipe-able fragments so position - -> 1-3 (total count 4)
I am not sure where your click happens to set the current item.
Maybe you'll find this useful:
Your MainFragment:
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
//init view
MainViewPageAdapter mainViewPageAdapter = new MainViewPageAdapter(getChildFragmentManager());
yourViewPager.setAdapter(mainViewPageAdapter);
return view;
}
private class MainViewPageAdapter extends FragmentStatePagerAdapter {
private static final int MAX_COUNT = 2;
public MainViewPageAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
switch (position) {
case 1:
fragment = YourChildFragmentOne.getInstance();
break;
case 2:
fragment = YourChildFragmentTwo.getInstance();
break;
}
return fragment;
}
#Override
public int getCount() {
return MAX_COUNT;
}
}
In your ChildFragment(s):
(or create an abstract ChildFragment, which handles the click listener and create two instances of the abstract one)
private OnChildFragmentClickListener mClickListener;
#Override
public void onAttach(Context context) {
super.onAttach(context);
mClickListener = (OnChildFragmentClickListener) getParentFragment();
}
//call somewhere in your ChildFragment mClickListener.onChildFragClick(index);
public interface OnChildFragmentClickListener{
void onChildFragClick(int index);
}
Now let your MainFragment implement OnChildFragmentClickListener and call there:
#Override
public void onChildFragClick(int index){
yourViewPager.setCurrentItem(index);
}

Viewpager fragments not shown after the first time

I have an activity with 3 fragments (A, B, C). Fragment A consists of a ViewPager with 2 ListFragments. The user can tap on an item in any of the listfragments and by doing so, goes to fragment B.
In fragment A I do:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
pagerAdapter = new PagerAdapter(getActivity().getSupportFragmentManager());
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragmentA, container, false);
vpPager = (ViewPager)view.findViewById(R.id.vpPager);
vpPager.setOffscreenPageLimit(2);
vpPager.setAdapter(pagerAdapter);
vpPager.addOnPageChangeListener(this);
return view;
}
And the PagerAdapter is as follows:
private class PagerAdapter extends FragmentPagerAdapter {
private final ListFragment1 lf1 = ListFragment1 .newInstance();
private final ListFragment2 lf2 = ListFragment2 .newInstance();
public PagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
switch (position) {
case 0: return lf1;
case 1: return lf2;
default: return null;
}
}
}
The first time the activity is shown, the viewpager list fragments are displayed correctly.
The 2 viewpager fragments load data from a db, and I do this only once (when the fragments are created).
The user can tap on an item and fragment B is displayed. If the user presses Back, fragment A is shown. However the list fragments are not shown (already an instance of them still exists).
Could it be that the view has been destroyed, even though instances exist?
What is wrong here? Is there a better approach?
EDIT
If I use newInstance in the pager adapter, I get an IllegalStateException: not attached to activity. This is because I start an async task as follows:
#Override
public void onPageSelected(int position) {
Fragment fragment = pagerAdapter.getItem(position);
if (fragment instanceof IPagedFragment) {
((IPagedFragment) fragment).onShown();
}
}
And onShown is:
#Override
public void onShown() {
myTask= new MyTask();
myTask.execute((Void)null);
}
When can I start the task so that I can be 100% sure that the fragment is attached to the activity and that the view has been created (I need to get listview, etc. from the layout).
You have to use ChildFragmentManager like below.
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
pagerAdapter = new PagerAdapter(getChildFragmentManager()); //here used child fragment manager
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragmentA, container, false);
vpPager = (ViewPager)view.findViewById(R.id.vpPager);
vpPager.setOffscreenPageLimit(2);
vpPager.setAdapter(pagerAdapter);
vpPager.addOnPageChangeListener(this);
return view;
}
It works like charm in my code with viewpager and fragment.
Just now I solved it after struggling for whole day, by using getChildFragmentManager()
pass this as a parameter to the pagerAdapter. and it will work.
while using pagerAdapter in fragment use :
PagerAdapter adapter = new PagerAdapter(getChildFragmentManager());
and in case of activity use getFragmentManager()
PagerAdapter adapter = new PagerAdapter(getFragmentManager());
You're creating ListFragment1 and ListFragment2 using the Activity FragmentManager, while you should use the Fragment FragmentManager. So, modify the pagerAdapter = new PagerAdapter(getActivity().getSupportFragmentManager()); with pagerAdapter = new PagerAdapter(getChildFragmentManager());. In this way, the fragments of the view pager will be 'bound' to the fragment hosting the viewpager. Moreover, you should not keep any reference to fragments inside the viewpager: it's something that Android already manage. Try with:
private class PagerAdapter extends FragmentPagerAdapter {
public PagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
switch (position) {
case 0: return ListFragment1.newInstance();
case 1: return ListFragment2.newInstance();
default: return null;
}
}
}
By the way, the vpPager.setOffscreenPageLimit(2); is unuseful since you have just 2 pages and this is a method that I've never used even when I have many fragments to manage, since it requires memory.
About your update: remove any logic related to ViewPager handling the fragment. If you need to start an AsyncTask within your Fragment, you can do it using one of the methods of Fragment lifecycle: onResume(), onCreateView() and so on.
class IPagedFragment extends Fragment {
public void onResume() {
super.onResume();
myTask= new MyTask();
myTask.execute((Void)null);
}
}
and please, remove the private final ListFragment1 lf1 = ListFragment1 .newInstance();. Trust me, it's not a good idea since you have a strong reference to your Fragments.
I've built a simple project that you can use as reference implementation. You can download the source code from my dropbox.
use getChildFragmentManager() instead of supportFragmentManager()
If any of the solutions above doesn't work, you can try a workaround by posting (delayed) to the pager view instance an additional notifyDataSetChanged call of the adapter:
vpPager.post(new Runnable() {
#Override
public void run() {
pagerAdapter.notifyDataSetChanged();
}
});
or
vpPager.postDelayed(new Runnable() {
#Override
public void run() {
pagerAdapter.notifyDataSetChanged();
}
}, 100 /* you have to find out the best delay time by trying/adjusting */);
Try overriding the getItemPosition method in your FragmentPagerAdapter:
#Override
public int getItemPosition(Object object) {
return PagerAdapter.POSITION_NONE;
}
If you experience this with Kotlin, it will be like this.
val fragmentAdapter = FragmentPageAdapter(childFragmentManager)
You shouldn't keep references to fragments in your FragmentPagerAdapter. You should always call newInstance in getItem() call, for example:
#Override
public android.support.v4.app.Fragment getItem(int position) {
switch (position) {
case 0: return ListFragment1.newInstance();
case 1: return ListFragment2.newInstance();
default: return null;
}
}
The data you load from the database should be stored in the fragment itself. The adapter will restore the state of fragments (setOffscreenPageLimit(2)).
You are losing your fragments because the items (fragments) are instantiated by the FragmentManager you provide, and it creates fragments based on tags. So it can happen that it creates a new instance of the fragment you already keep, just with different tag.
See FragmentPagerAdapter source code (check instantiateItem() method):
https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v13/java/android/support/v13/app/FragmentPagerAdapter.java
Also see this answer:
keep instances of fragments inside FragmentPagerAdapter
On PagerAdapter class override the method setPrimaryItem,
which is called when there's a change in the pager, i would give it a shot.
I would create something like :
private class PagerAdapter extends FragmentPagerAdapter {
private final ListFragment1 lf1 = ListFragment1 .newInstance();
private final ListFragment2 lf2 = ListFragment2 .newInstance();
public PagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
switch (position) {
case 0: return lf1;
case 1: return lf2;
default: return null;
}
}
#Override
public int getCount() {
return 2;
}
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
if (position == 0)
lf1.updateUI(); //Refresh what you need on this fragment
else if (position == 1)
lf2.updateUI();
}
}
You're missing getCount() as well.
I'm not sure offscreen has any use, but its probably not an issue. vpPager.setOffscreenPageLimit(2)
One more thing, i would also remove vpPager.addOnPageChangeListener(this), there's no use for this, an it might cause you some issues.
Whatever you need to do, you can pull it off without it, by overriding the pagination, you might "ruin" some of the standard pagination(since the super isn't called)

ViewPager Fragments are not initiated in onCreate

I seem to be having an issue updating the fragments that I am using in my ViewPager, regardless of whether I try in onCreate(), onCreateView(), or onResume(). Here is how I'm setting up my ViewPager in my MainFragment:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main_screen, container, false);
mPager = (ViewPager)rootView.findViewById(R.id.pager);
mPager.setOffscreenPageLimit(2); // So all 3 pages are loaded at once.
mAdapter = new JournalPagerAdapter(getActivity().getSupportFragmentManager(), this);
mPager.setAdapter(mAdapter);
// Add bar graph to view
mGraphLayout = (LinearLayout) rootView.findViewById(R.id.journalGraph);
updateGraph();
mGraphLayout.addView(mGraphView);
mPainFrag = (PainFragment)mAdapter.getRegisteredFragment(0);
// Null pointer here, but if I put the action in a button listener, it works.
mPainFrag.setScale(mEntry.getPain());
...
I'm accessing the fragments through some overridden methods in my FragmentPagerAdapter:
public class JournalPagerAdapter extends FragmentPagerAdapter {
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
private View.OnClickListener mOnClickListener;
public JournalPagerAdapter(FragmentManager mgr, View.OnClickListener onClickListener) {
super(mgr);
mOnClickListener = onClickListener;
}
#Override
public Fragment getItem(int pos) {
switch(pos) {
case 0: return PainFragment.newInstance("PainFragment", mOnClickListener);
case 1: return StressFragment.newInstance("StressFragment", mOnClickListener);
case 2: return SleepFragment.newInstance("SleepFragment", mOnClickListener);
default: return PainFragment.newInstance("PainFragment", mOnClickListener);
}
}
#Override
public int getCount() {
return 3;
}
/* Thanks to Streets of Boston (http://stackoverflow.com/questions/8785221/retrieve-a-fragment-from-a-viewpager)
*/
#Override
public Object instantiateItem(ViewGroup container, int position) {
Log.v("rx", "itemInstantiated");
Fragment fragment = (Fragment) super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
I can't seem to figure out why the fragment is null right after I set the adapter, but if I put the fragment update code in a click event, I have no issues.
I would try adding a layout listener to your ViewPager to get notified when the laying out of views has occurred.
When you create your ViewPager call mPager.getViewTreeObserver().addOnGlobalLayoutListener() and pass something implementing OnGlobalLayoutListener.
In the callback method, do your fragment updating. Make sure to call mPager.getViewTreeObserver().removeGlobalOnLayoutListener(this) in the callback, otherwise the callback will be called multiple times.
You could instantiate your fragments in the PagerAdapter's constructor and just have getItem return them instead of instantiating them.

Categories

Resources