I am trying to wrap my head around the proper use of fragments with the ViewPager. I currently have 5 fragments (with one main activity) each of which pulls some data from a web service and displays it to the user. The user can swipe between the views to see the different info. The problem I am running into is the fragments get destroyed and recreated once the user swipes two pages away and then back to the fragment resulting in multiple calls to the web services when they are not needed. Configuration changes also force the fragments to be recreated (thus recalling the webservice). I am currently recreating a new instance of the fragment every time. Whats the best way to cache the data? Or am I using FragmentPagerAdapter in the wrong way? I've attached some relevant code.
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
WebFragmentOne a = WebFragmentOne.newInstance();
return a;
case 1:
WebFragmentTwo b = WebFragmentTwo.newInstance();
return b;
case 2:
WebFragmentThree c = WebFragmentThree.newInstance();
return c;
case 3:
WebFragmentFour d = WebFragmentFour.newInstance();
return d;
case 4:
WebFragmentFive f = WebFragmentFive.newInstance();
return f;
}
return null;
}
The newInstance() method in each of the fragments.
public static final WebFragment newInstance()
{
WebFragment f = new WebFragment();
return f;
}
Keep the data you want to keep in a private field within the fragment.
In the OnCreate call setRetainInstance(true) to stop Android destroying your fragment.
Now you can check when the fragment starts if you already have data. If you don't then you can retrieve it from your web service. If you do then skip the retrieval and jump straight to the setting up of your views.
Related
Im creating a app with three sections home,color,profile I'm using bottom navigation view with fragments to achieve this.
App UI
Here is my code:
Main Activity on create method:
final HomeFragment homeFragment = new HomeFragment();
final ColorFragment colorFragment = new ColorFragment();
final ProfileFragment profileFragment = new ProfileFragment();
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment active = homeFragment;
switch (item.getItemId()){
case R.id.action_home:
active = homeFragment;
break;
//return true;
case R.id.action_color:
active = colorFragment;
break;
case R.id.action_profile:
active = profileFragment;
break;
default:
active = homeFragment;
break;
}
//Loading the fragment on click
fragmentManager.beginTransaction().replace(R.id.fragment_container,active).commit();
return true;
}
});
Im using fragment manager to load the fragment in my Main Activity which comes with below problem.
1.It reloads the fragment each time I click on the bottom navigation icon.(Im getting data from firebase this will be costly )
I have read many articles regarding this,i found solutions like below:
Using hiding the fragments on creation of activity
fragmentManager.beginTransaction().add(R.id.fragment_container, profileFragment, "3").hide(profileFragment).commit();
fragmentManager.beginTransaction().add(R.id.fragment_container, colorFragment, "2").hide(colorFragment).commit();
fragmentManager.beginTransaction().add(R.id.fragment_container,homeFragment, "1").commit();
Above solution loads all the fragments on the creation of activity which isn't a efficient scnario
Using NavHost :
It refresh the fragment every time.
This also not working for me.
Please Help:
What I want to acheive:
1.The fragment should only load when I click on the icon for the first time.
2.All the fragments should not load at the time of activity creation.
3.When I revist the fragment It should not load again, I should stay at previous state ex. scrolled till 25th card
//Its should be like playstore,youtube where when we click on icon it loads the data, when we revisit the state will be there.
You either need to use extension function provided by Google for BottomNavigationView or use a ViewPager/ViewPager2 to have each fragment have it's own back stack and going back to exact same fragment instead of creating root fragment over and over again. Both solutions require you to have NavHostFragment as root of each tab. You can check out the samples in this repo.
You can either use a FragmentPagerAdapter or ViewPager2. I refer you to two answers for a similar question:
1- FragmentPagerAdapter
2- ViewPager2
I 'm using a viewpager as a questionnaire. The user inputs their answers in the first 8 fragments, then their answers are displayed in the 10th fragment (OverviewFragment). It's all working great. The issue I have a question about is when the user returns to a past fragment, ie: Fragment 4 and changes an answer, if they return to the 10th fragment it doesn't update. I'm sure this is a lifecycle/backstack issue but unsure how to kick fragments off the backstack in a viewpager. Or maybe just a way to ensure that fragment is constantly refreshed?
public class SectionsPagerAdapter extends FragmentStatePagerAdapter {
private int fragmentCount = 11;
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position){
case 0:
return DemoFragment.newInstance();
case 1:
return PreliminaryQuesFragment.newInstance();
case 2:
return QuestionOneFragment.newInstance();
case 3:
return QuestionTwoFragment.newInstance();
case 4:
return QuestionThreeFragment.newInstance();
case 5:
return QuestionFourFragment.newInstance();
case 6:
return QuestionFiveFragment.newInstance();
case 7:
return QuestionSixFragment.newInstance();
case 8:
return StatementFragment.newInstance();
case 9:
return OverviewFragment.newInstance(user.getTitle(), prelimAnswer, qOne, qTwo,
qThree, qFour, qFive);
case 10:
return GoodbyeFragment.newInstance(position);
default:
return null;
}
}
#Override
public int getCount() {
return fragmentCount;
}
}
It's likely an issue with how your Fragment's state is being restored when a Fragment that has been previously created is navigated back to -- is your FragmentStatePagerAdapter really creating a NEW fragment instance, or reusing an old one it's already been created, and using it's previous Bundle args?
I've run into a similar issue when creating Fragment's and passing a full Parcelable object as the UI Model to use when populating the Fragment's UI. Once the Fragment was navigated back to, the original Parcelable was used when recreating the Fragment's View (so, the old, original object from the Bundle args I set on the Fragment on it's initial creation). To get around this issue, I passed an ID reference to each Fragment in it's Bundle args instead (so, just some integer or something), that mapped to the present, in memory copy of the model Object I was trying to display in the UI.
So, even though my Fragment was recreated with the same bundle args, I would use SomeInMemoryHelperObjectContainingMyMap.get(ID) to get the most recent version of that object to populate the UI.
Okay i'll try and make this as clear as possible. I have a Fragment called CheckerManager which contains a ViewPager. This ViewPager will allow the user to swipe between 3 Fragments all of which are an instance of another Fragment called CheckerFragment. I'm using a FragmentPagerAdapter to handle paging. Here's how it looks
private class PagerAdapter extends FragmentPagerAdapter {
CharSequence mTabTitles[];
public PagerAdapter(FragmentManager fm, CharSequence tabTitles[]) {
super(fm);
mTabTitles = tabTitles;
}
#Override
public Fragment getItem(int position) {
switch(position) {
case 0:
return CheckerFragment.newInstance(MainFragment.DRAW_TITLE_LOTTO);
case 1:
return CheckerFragment.newInstance(MainFragment.DRAW_TITLE_DAILY);
case 2:
return CheckerFragment.newInstance(MainFragment.DRAW_TITLE_EURO);
default:
return null;
}
}
#Override
public CharSequence getPageTitle(int position) {
return mTabTitles[position];
}
#Override
public int getCount() {
return 3;
}
}
I know that the ViewPager will always create the Fragment either side of the current Fragment. So say my 3 CheckerFragments are called A, B and C and the current Fragment is A. B has already been created. But my problem is that even though I am still looking at Fragment A, Fragment B is the 'active' Fragment. Every input I make is actually corresponding to Fragment B and not A. The active Fragment is always the one which has been created last by the ViewPager.
I've looked at quite a few things to see if anyone has had the same problem but i'm finding it difficult to even describe what's wrong. I think it's something to with the fact that all of the ViewPagers fragments are of the same type ie - CheckerFragment. I have a working implementation of a ViewPager inside a fragment elsewhere in the application and the only difference I can tell is that each page is a different type of Fragment.
Any help would be appreciated!
*EDIT
PagerAdapter adapter = new PagerAdapter(getChildFragmentManager(), tabTitles);
ViewPager viewPager = (ViewPager)view.findViewById(R.id.viewPagerChecker);
viewPager.setAdapter(adapter);
I feel pretty stupid but I found out what the issue was. In my CheckerFragment I would call getArguments() to retrieve a String extra and I would use this to determine how to layout the fragment. Problem was I made this extra a static member of CheckerFragment. So every time a new Fragment was created it was using the most recent extra.
Moral of the story - Don't make your fragments extra a static member if you plan on making multiple instances of that fragment.
I am using a ViewPager with 4 pages, and I'm looking for an efficient way to replace/switch between fragments in each page.
This is the interaction pattern that I'm trying to create:
User presses a button on a page that currently holds fragment A
Fragment A is swapped out for some new fragment B
The user does some work in fragment B, and then presses a button when he/she is done
Fragment B is removed, and is replaced by fragment A (the original fragment)
I've found a way to do this, but it seems to have significant flaws. The solution involves removing the original fragment, and then overriding getItemPosition (essentially the method described in this related question):
//An array to keep track of the currently visible fragment in each page
private final Fragment[] activeFragments= new Fragment[4];
public void openFragmentB(ViewPager pager, int position) {
startUpdate(pager);
//Remove the original fragment
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.remove(activeFragments[position]);
transaction.commit();
//Create a new tile search fragment to replace the original fragment
activeFragments[position] = FragmentB.newInstance();
pageStates[position] = PageState.STATE_B;
finishUpdate(pager);
notifyDataSetChanged();
}
#Override
public int getItemPosition(Object object) {
//If the main fragment is not active, return POSITION_NONE
if(object instanceof FragmentA) {
FragmentA a = (FragmentA) object;
if(pageStates[a.getPosition()] != PageState.STATE_A) {
return POSITION_NONE;
}
}
//If the secondary fragment is not active, return POSITION_NONE
if(object instanceof FragmentB) {
FragmentB b = (FragmentB) object;
if(pageStates[b.getPosition()] != PageState.STATE_B) {
return POSITION_NONE;
}
}
return POSITION_UNCHANGED;
}
This method works, but has undesirable side effects. Removing the fragment and setting it's position to POSITION_NONE causes the fragment to be destroyed. So when the user finishes using FragmentB, I would need to create a new instance of FragmentA instead of reusing the original fragment. The main fragments in the pager (FragmentA in this example) will contain relatively large database backed lists, so I want to avoid recreating them if possible.
Essentially I just want to keep references to my 4 main fragments and swap them in and out of pages without having to recreate them every time. Any ideas?
A simple way to avoid recreating your Fragments is to keep them as member variables in your Activity. I do this anyway in conjunction with onRetainCustomNonConfigurationInstance() order to retain my fragments during configuration changes (mostly screen rotation). I keep my Fragments in a 'retainer' object since onRetainCustomNonConfigurationInstance only returns a single object.
In your case, instead of calling Fragment.newInstance() all the time, just check to see if the fragments contained in the retainer object is null before creating a new one. If it isn't null, just re-use the previous instance. This checking should happen in your ViewPager adapter's getItem(int) method.
In effect, doing this basically means you are handling whether or not Fragments are recycled when getItem is called, and overriding the getItemPosition(Object) method to always return POSITION_NONE when for relevant Segments.
FragmentPagerAdapter provides an overrideable method called getItemId that will help you here.
If you assign a unique long value to each Fragment in your collection, and return that in this method, it will force the ViewPager to reload a page when it notices the id has changed.
Better late than never, I hope this helps somebody out there!
I have a viewpager that pages through fragments. My FragmentPagerAdapter subclass creates a new fragment in the getItem method which seems wasteful. Is there a FragmentPagerAdapter equivalent to the convertView in the listAdapter that will enable me to reuse fragments that have already been created? My code is below.
public class ProfilePagerAdapter extends FragmentPagerAdapter {
ArrayList<Profile> mProfiles = new ArrayList<Profile>();
public ProfilePagerAdapter(FragmentManager fm) {
super(fm);
}
/**
* Adding a new profile object created a new page in the pager this adapter is set to.
* #param profile
*/
public void addProfile(Profile profile){
mProfiles.add(profile);
}
#Override
public int getCount() {
return mProfiles.size();
}
#Override
public Fragment getItem(int position) {
return new ProfileFragment(mProfiles.get(position));
}
}
The FragmentPagerAdapter already caches the Fragments for you. Each fragment is assigned a tag, and then the FragmentPagerAdapter tries to call findFragmentByTag. It only calls getItem if the result from findFragmentByTag is null. So you shouldn't have to cache the fragments yourself.
Appendix for Geoff's post:
You can get reference to your Fragment in FragmentPagerAdapter using findFragmentByTag(). The name of the tag is generated this way:
private static String makeFragmentName(int viewId, int index)
{
return "android:switcher:" + viewId + ":" + index;
}
where viewId is id of ViewPager
Look at this link: http://code.google.com/p/openintents/source/browse/trunk/compatibility/AndroidSupportV2/src/android/support/v2/app/FragmentPagerAdapter.java#104
Seems a lot of the people viewing this question are looking for a way to reference the Fragments created by FragmentPagerAdapter/FragmentStatePagerAdapter. I would like to offer my solution to this without relying on the internally created tags that the other answers on here use.
As a bonus this method should also work with FragmentStatePagerAdapter. See notes below for more detail.
Problem with current solutions: relying on internal code
A lot of the solutions I've seen on this and similar questions rely on getting a reference to the existing Fragment by calling FragmentManager.findFragmentByTag() and mimicking the internally created tag: "android:switcher:" + viewId + ":" + id. The problem with this is that you're relying on internal source code, which as we all know is not guaranteed to remain the same forever. The Android engineers at Google could easily decide to change the tag structure which would break your code leaving you unable to find a reference to the existing Fragments.
Alternate solution without relying on internal tag
Here's a simple example of how to get a reference to the Fragments returned by FragmentPagerAdapter that doesn't rely on the internal tags set on the Fragments. The key is to override instantiateItem() and save references in there instead of in getItem().
public class SomeActivity extends Activity {
private FragmentA m1stFragment;
private FragmentB m2ndFragment;
// other code in your Activity...
private class CustomPagerAdapter extends FragmentPagerAdapter {
// other code in your custom FragmentPagerAdapter...
public CustomPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
// Do NOT try to save references to the Fragments in getItem(),
// because getItem() is not always called. If the Fragment
// was already created then it will be retrieved from the FragmentManger
// and not here (i.e. getItem() won't be called again).
switch (position) {
case 0:
return new FragmentA();
case 1:
return new FragmentB();
default:
// This should never happen. Always account for each position above
return null;
}
}
// Here we can finally safely save a reference to the created
// Fragment, no matter where it came from (either getItem() or
// FragmentManger). Simply save the returned Fragment from
// super.instantiateItem() into an appropriate reference depending
// on the ViewPager position.
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
// save the appropriate reference depending on position
switch (position) {
case 0:
m1stFragment = (FragmentA) createdFragment;
break;
case 1:
m2ndFragment = (FragmentB) createdFragment;
break;
}
return createdFragment;
}
}
public void someMethod() {
// do work on the referenced Fragments, but first check if they
// even exist yet, otherwise you'll get an NPE.
if (m1stFragment != null) {
// m1stFragment.doWork();
}
if (m2ndFragment != null) {
// m2ndFragment.doSomeWorkToo();
}
}
}
or if you prefer to work with tags instead of class member variables/references to the Fragments you can also grab the tags set by FragmentPagerAdapter in the same manner:
NOTE: this doesn't apply to FragmentStatePagerAdapter since it doesn't set tags when creating its Fragments.
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
// get the tags set by FragmentPagerAdapter
switch (position) {
case 0:
String firstTag = createdFragment.getTag();
break;
case 1:
String secondTag = createdFragment.getTag();
break;
}
// ... save the tags somewhere so you can reference them later
return createdFragment;
}
Note that this method does NOT rely on mimicking the internal tag set by FragmentPagerAdapter and instead uses proper APIs for retrieving them. This way even if the tag changes in future versions of the SupportLibrary you'll still be safe.
Don't forget that depending on the design of your Activity, the Fragments you're trying to work on may or may not exist yet, so you have to account for that by doing null checks before using your references.
Also, if instead you're working with FragmentStatePagerAdapter, then you don't want to keep hard references to your Fragments because you might have many of them and hard references would unnecessarily keep them in memory. Instead save the Fragment references in WeakReference variables instead of standard ones. Like this:
WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
// reference hasn't been cleared yet; do work...
}
If the fragment still in memory you can find it with this function.
public Fragment findFragmentByPosition(int position) {
FragmentPagerAdapter fragmentPagerAdapter = getFragmentPagerAdapter();
return getSupportFragmentManager().findFragmentByTag(
"android:switcher:" + getViewPager().getId() + ":"
+ fragmentPagerAdapter.getItemId(position));
}
Sample code for v4 support api.
For future readers!
If you are thinking of reusing fragments with viewpager, best solution is to use ViewPager 2, since View Pager 2 make use of RecyclerView.
Medium article - Exploring the View Pager 2
Docs
Samples repo
Release notes
I know this is (theoretically) not an answer to the question, but a different approach.
I had an issue where I needed to refresh the visible fragments. Whatever I tried, failed and failed miserably...
After trying so many different things, I have finally finish this using BroadCastReceiver. Simply send a broadcast when you need to do something with the visible fragments and capture it in the fragment.
If you need some kind of a response as well, you can also send it via broadcast.
cheers