Use one Fragment in a ViewPager multiple times - android

Is it possible to use one fragment in a viewpager multiple times? I am trying to build a dynamically updated UI using ViewPager.
I want to use the same design, basically the same fragment with different data for every page, like a listview adapter.

You can instantiate the same Fragment class for every page in your ViewPager, passing the position of the ViewPager to control what to display. Something like that:
public class MyFragment extends Fragment {
private int mIndex;
public MyFragment(int index) {
mIndex = index;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
switch(mIndex){
case 0:
// do you things..
case 1:
// etcetera
}
}
}
then, in you FragmentPagerAdapter:
public static class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
return new MyFragment(position);
}
}
That way you can reuse most of your code changing only what you need in the switch/case statement.

You misunderstood the concept of class versus object of class. Your MyFragment.java source code defines class which you turn into "living thing" each time you instantiate it with new operator (new MyFragment();) - this creates object which is an instance of your class. Unless you intentionally prevent this (by i.e. using Singleton pattern) your can make as many instances of the class as you like, same way you are allowed to make as many i.e. cakes using single recipe. And this applies to fragments as well.
So as long as you create separate object (aka said instance) of your class for each page, you should be able to do what you want.

Related

Android viewPager2 FragmentStateAdapter with variable fragments

I am trying to implement viewPager2 with variable number of fragments, and not sure on the correct / best approach and couldn't find any good examples to copy!
The fragments in my viewpager depend on the properties of the object being displayed - the first 2 are always present, but there are 4 more that may or may not be present - i.e. the FragmentStateAdapter does not know how many fragments there will be or which ones will be present until runtime.
My initial approach was to have a List that I passed to my FragmentStateAdapter which looked like this:
public class busDetailFragmentAdapter extends FragmentStateAdapter {
private final List<Fragment> fragmentList;
public busDetailFragmentAdapter(#NonNull Fragment fragmentActivity, List<Fragment> fragmentList) {
super(fragmentActivity);
this.fragmentList = fragmentList;
}
// return fragments at every position
#NonNull
#Override
public Fragment createFragment(int position) {
return fragmentList.get(position);
}
#Override
public int getItemCount() {
return fragmentList.size();
}
}
I then simply added fragments to my list with tabFragments.add(new busDetailInfoFrag()); and notified the fragmentadapter that an item had been inserted.
This worked fine until I navigated away from the parent fragment and then used the back button - the app crashed with a "Fragment already added" error.
I found something in the docs creating about new fragments and not reusing fragment. So, I modified my adapter to use a list of strings rather than a list of fragments, and am looking up the position against this list and using a switch statement to create the correct fragment.
public class busDetailFragmentAdapter extends FragmentStateAdapter {
private final List<String> fragmentList;
public busDetailFragmentAdapter(#NonNull Fragment fragmentActivity, List<String> fragmentList) {
super(fragmentActivity);
this.fragmentList = fragmentList;
}
// return fragments at every position
#NonNull
#Override
public Fragment createFragment(int position) {
switch (fragmentList.get(position)){
case "info":
return new busDetailInfoFrag();
case "location":
return new busDetailLocationFrag();
//More cases as needed
default:
return new missingFrag();
}
}
#Override
public int getItemCount() {
return fragmentList.size();
}
}
This is working as I need it to, but just wondering if this is the best/correct approach or if there's a better way!

Fragment Field is NULL after rotate device in Android

When I start the app everything works ok but when I rotate to landscape it crashes because in the Fragment there is a field that is NULL.
I dont use setRetainInstance(true) or adding Fragments to FragmentManagerI create new Fragments on app start and when app rotate.
In the Activity OnCreate() I create the Fragment and adding them to the viewPager like this.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ParentBasicInfoFragment parentBasicInfoFragment = new ParentBasicInfoFragment();
ParentUTCFragment parentUTCFragment = new ParentUTCFragment();
ParentEventsFragment parentEventsFragment = new ParentEventsFragment();
this.mFragments = new ArrayList<>();
this.mFragments.add(parentBasicInfoFragment);
this.mFragments.add(parentUTCFragment);
this.mFragments.add(parentEventsFragment);
this.viewpage.setOffscreenPageLimit(3);
setCurrentTab(0);
this.viewpage.setAdapter(new MainActivityPagerAdapter(getSupportFragmentManager(), this.mFragments));
}
Then I have a test button on the app that when I press it will do like
public void test(View view) {
((BaseFragment) MainActivity.this.mFragments.get(MainActivity.this.viewpage.
getCurrentItem())).activityNotifiDataChange("hello");
}
This will work and the current Fragments in the ViewPager have the method, activityNotifiDataChange() that are being called and all is ok.
When I rotate the app and do the same thing pressing the button the activityNotifiDataChange() is being called alright but there a null pointer exception because the ArrayList<Fragment> mFragment is now NULL.
Here´s a small sample Android Studio project showing this behavior:
https://drive.google.com/file/d/1Swqu59HZNYFT5hMTqv3eNiT9NmakhNEb/view?usp=sharing
Start app and press button named "PRESS TEST", then rotate device and press the button again and watch the app crash
UPDATE SOLUTION thanks #GregMoens and #EpicPandaForce
public class MainActivityPagerAdapter extends PersistenPagerAdapter<BaseFragment> {
private static int NUM_ITEMS = 3;
public MainActivityPagerAdapter(FragmentManager fm) {
super(fm);
}
public int getCount() {
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return ParentBasicInfoFragment.newInstance(0, "Page # 1");
case 1:
return ParentUTCFragment.newInstance(1, "Page # 2");
case 2:
return ParentEventsFragment.newInstance(2, "Page # 3");
default:
return null;
}
}
}
public abstract class PersistenPagerAdapter<T extends BaseFragment> extends FragmentPagerAdapter {
private SparseArray<T> registeredFragments = new SparseArray<T>();
public PersistenPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
#Override
public T instantiateItem(ViewGroup container, int position) {
T fragment = (T)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 T getRegisteredFragment(ViewGroup container, int position) {
T existingInstance = registeredFragments.get(position);
if (existingInstance != null) {
return existingInstance;
} else {
return instantiateItem(container, position);
}
}
}
The main problem I see with your app is your misunderstanding with how FragmentPagerAdapter works. I see this a lot and it's due to lack of good javadocs on the class. The adapter should be implemented so that getItem(position) returns a new fragment instance when called. And then getItem(position) will only be called by the pager when it needs a new instance for that position. You should not pre-create the fragments and pass then into the adapter. You should also not be holding strong references to the fragments from either your activity or from parent fragments (like ParentBasicInfoFragment). Because remember, the fragment manager is managing fragments and you are also managing fragments by newing them and keeping references to them. This is causing a conflict and after rotation, you are trying to invoke activityNotifiDataChange() on a fragment that is not actually initialized (onCreate() was not called). Using the debugger and tracking object IDs will confirm this.
If you change your code so that the FragmentPagerAdapter creates the fragments when they are needed and don't store references to fragments or lists of fragments, you will see much better results.
this.mFragments = new ArrayList<>();
Because this is wrong. You should never hold a reference to the fragment list if you are using ViewPager. Just return new instance of Fragment from getItem(int position) of FragmentPagerAdapter and you are good to go.
But to fix your code, you must delete mFragments entirely.
See https://stackoverflow.com/a/58605339/2413303 for more details.
Use setRetainInstance(true) is not a good approach. If you need to same some simple information such as: position of recyclerView, selected item of recyclerView, maybe some model(Parcelable) you could do it with method onSaveInstanceState / onRestoreInstanceState, there is one limitation is 1MB. Here is an example with animations how it works.
For more durable persistance use SharedPreferences, Room(Google ORM) or you could try to use ViewModel with LiveData (best approach some data which should live while user session).
//change in AndroidManifest.xml file,
<activity
android:name=".activity.YourActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:screenOrientation="sensor"
/>
//YourActivity in which you defines fragment.
//may be it helps

How to reuse layouts/fragments in ViewPager

Hi there I am wondering what is the correct way if I would like to add different instance of a Fragment which uses the same Layout with different content in a ViewPager.
So for a better understanding I create my Fragments with an createInstance() method and pass an id with which it gets content for a list from a database. I add them to my FragmentPagerAdapter.
So what I get is the first Fragment added multiple times without getting its content for the actual id.
How could I force the ViewPager to treat the Fragments individually ?
*Edit:
Okay I totally failed within my createInstance() method which was more a getInstance() method and returned the same instance every time...
You need to set adapter.notifyDataSetChanged.
Refer below
class MyFragmentAdapter extends FragmentPagerAdapter{
public MyFragmentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return new PagerFragment(arrayListUri.get(position),position);
}
#Override
public int getCount() {
return 0;
}
}

Dynamically Add and Remove Fragments From FragmentPagerAdapter

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.

ViewpagerIndicator use with SherlockFragmentActivity or ListFragment

In my project im using actionBarSherlok library and is working fine.
I want to implement the google play app, beautiful widgets effect. For that i found the following library.
And also works. But my problem is how can i combine the secod librery to be use with listfragment or with SherlockFragmentActivity (which i prefer).
Example code:
- TestFragment is just a Fragment class. getItem is the method that returns the fragment that will be show when the user move the finger on the screen right? If its an #override method how can i show on screen other class than Fragment?
Do i need to create an adapter, if this is the case how i know what i have to keep in mind? or is there one for this type of things?
I think im missing something.
class GoogleMusicAdapter extends TestFragmentAdapter implements TitleProvider {
public GoogleMusicAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return TestFragment.newInstance(SampleTabsDefault.CONTENT[position % SampleTabsDefault.CONTENT.length]);
}
#Override
public int getCount() {
return SampleTabsDefault.CONTENT.length;
}
#Override
public String getTitle(int position) {
return SampleTabsDefault.CONTENT[position % SampleTabsDefault.CONTENT.length].toUpperCase();
}
}
Regards.

Categories

Resources