I'm developing an Android Application where I have a ViewPager in my MainActivity, consisting of 3 Fragments.
The Problem is as follows, when I swipe to the 3rd fragment, and get back to the 1st one, onCreateView of fragment 1 is called which is what I really want, however, If I swipped to the 2nd fragment only, and returned to the 1st one, onCreateView is not called!
I need to find a solution to this problem, I spent hours trying to find a solution but didn't know to solve it.
Here's the code related to the ViewPager in my MainActivity
private void setupViewPager(final ViewPager viewPager) {
final ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.addFrag(new voting(), "ONE");
adapter.addFrag(new dashboard(), "TWO");
adapter.addFrag(new chats(), "THREE");
myAdapter=adapter;
viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(0);
}
private class ViewPagerAdapter extends FragmentStatePagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<Fragment>();
private final List<String> mFragmentTitleList = new ArrayList<String>();
private ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position) {
Log.i("getItem","getItem");
return mFragmentList.get(position);
}
#Override
public int getCount() {
return mFragmentList.size();
}
private void addFrag(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
//added newly
#Override
public int getItemPosition(Object object){
return PagerAdapter.POSITION_NONE;
}
#Override
public CharSequence getPageTitle(int position) {
return null;
}
}
This is intended behavior with a FragmentPagerAdapter. If the fragment was already created and is still in memory, it is just reattached to the ViewPager. Hence, the reason why onCreateView(...) isn't called a second time.
Implementation of PagerAdapter that represents each page as a Fragment that is persistently kept in the fragment manager as long as the user can return to the page.
You're using a FragmentStatePagerAdapter which is a subclass of FragmentPagerAdapter, so it inherits this behavior.
Generally, it's not a good idea to recreate a view on every page change, as that is expensive and can lead to non smooth scrolling. If you had some logic in onCreateView(...) that you want to run again on each fragment change, then I suggest looking into ViewPager.OnPageChangeListener interface.
Related
I have an activity named: ShoppingListActivity in which i have added 3 fragments. For this example i will use only one fragment. In onCreate method i have declared a ViewPager that looks like this:
ViewPager viewPager = (ViewPager) findViewById(R.id.view_pager);
and a setupViewPager(viewPager); method that looks like this:
private void setupViewPager(ViewPager viewPager) {
ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
Fragment shoppingListFragment = new ShoppingListFragment();
getSupportFragmentManager().beginTransaction().add(shoppingListFragment, "ShoppingListFragmentTag").commit();
adapter.addFragment(shoppingListFragment, "SHOPPING LIST");
viewPager.setAdapter(adapter);
}
My ViewPagerAdapter class lookslike this:
private class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> fragmentList = new ArrayList<>();
private final List<String> fragmentTitleList = new ArrayList<>();
ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
#Override
public int getCount() {
return fragmentList.size();
}
void addFragment(Fragment fragment, String title) {
fragmentList.add(fragment);
fragmentTitleList.add(title);
}
#Override
public CharSequence getPageTitle(int position) {
return fragmentTitleList.get(position);
}
}
My problem is when i go from ShoppingListActivity to another activity and than come back, my fragments are not refreshed. This is my code from onResume method in which i try to find the fragment to detach and attach the fragment like this:
FragmentManager fragmentManager = getSupportFragmentManager();
ShoppingListFragment shoppingListFragment = (ShoppingListFragment) fragmentManager.findFragmentByTag("ShoppingListFragmentTag");
fragmentManager.beginTransaction().detach(shoppingListFragment).attach(shoppingListFragment).commit();
The problem is that i get this error: java.lang.IllegalStateException: Can't change tag of fragment ShoppingListFragment{528673f4 #0 ShoppingListFragmentTag}: was ShoppingListFragmentTag now android:switcher:2131558559:0
How can i set the Tag correctly to the fragment?
Thanks in advance!
from the code what I understand is, There is a ViewPager implemented in ShoppingListActivity along with tabs.
In viewpager each fragment contains the list of something which come from server right? Now you want to refresh whole viewpager when user back from the ShoppingListInfoActivity right?
Do below changes in you ShoppinglistActivity.
1) Declare ViewPagerAdapter adapter; at class level global.
2)change private class ViewPagerAdapter extends FragmentPagerAdapter
replace FragmentPagerAdapter to FragmentStatePagerAdapter
3) change in onresume()
#Override
protected void onResume() {
super.onResume();
if(adapter!=null){
adapter.notifyDataSetChanged();
}
}
I used a TabLayout for a tabbed view in my app. I added three List fragments - one for each tab.
The content of either of the fragments is supposed to be changed (and the list views have to be updated) when new record is added/removed from any of the fragments.
But after I modify some data and swiped to the next tab the list view is not updated and retained the old data.
I created an interface that is implemented in all of the ListFragments as follows:
public interface UpdatableFragment {
void updateContent();
}
public class LogFragment implements UpdatableFragment{
...
#Override
public void updateContent() {
Activity activity = getActivity();
if (isAdded() && activity != null) {
getLoaderManager().restartLoader(LOG_LIST_LOADER, null, this);
}
}
...
}
And I call the method in the MainActivity tab select listener
tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
if(adapter != null){
int position = viewPager.getCurrentItem();
viewPager = (ViewPager) findViewById(R.id.viewpager);
viewPager.setCurrentItem(position);
Fragment tabbedFragment = adapter.getItem(viewPager.getCurrentItem());
if(tabbedFragment instanceof UpdatableFragment){
((UpdatableFragment) tabbedFragment).updateContent();
}
}
}
But the condition if (isAdded() && activity != null) in updateContent() method sometimes returns false because activity becomes null. On other times, it returns the MainActivity instance and the loader restarts.
Why is this inconsistent behavior happening and how can I make the ListFragments always show fresh content when their corresponding tabs are selected?
EDIT
adding the fragment pager code
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);
}
}
The getItem(int position) method does not return a reference to the Fragment currently in that position of your ViewPager. The getItem() method is used in the PagerAdapter when it calls instantiateItem() to create your views/fragments, so when you call getItem(int position) it's creating a brand new Fragment for you, and this is not added/attached to your Activity (causing the false/null in your if check). If you look into the code for FragmentPagerAdapter.instantiateItem() you can see where it calls getItem() and then adds the Fragment with the FragmentManager.
One way to get around this is to cache your Fragment references in your FragmentPagerAdapter (do this by caching the Fragment returned by instantiateItem() and remove the references in adapter.destroyItem()), or calling adapter.notifyDataSetChanged() to re-create your views with the updated data.
UPDATE:
You want to add something like this to your FragmentPagerAdapter:
public class Adapter extends FragmentPagerAdapter {
private SparseArray<Fragment> fragmentCache = new SparseArray<>();
#Override public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
fragmentCache.put(position, fragment);
return fragment;
}
#Override public void destroyItem(ViewGroup container, int position, Object object) {
fragmentCache.remove(position);
super.destroyItem(container, position, object);
}
// other code....
}
You can then get references to all the fragments currently in your adapter from the fragmentCache, and then you can call your updateContent method on them.
I am using Tab layout with view pager and adding fragments dynamically. Now I need to update a TextView which present in ViewPagers fragment, according to swipe of fragment or click of tab. Suppose if I click on first tab or swipe my fragments then TextView in OperatorPlanList fragment is updtate with text like "I just click".
Note - TextView is present in layout of fragment.
When I try to find position by using position in get view method, FragmentStatePageAdapter, then I found out it update position twice when first or last fragment is seen.
So how can I update my fragment dynamically according to current fragment seen on screen in ViewPager???
This is my FragmentStatePagerAdapter adapter.
class ViewPagerFinder extends FragmentStatePagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerFinder(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position){
return mFragmentList.get(position);
}
#Override
public int getCount() {
System.out.println("getcount>>>" + mFragmentList.size());
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
return super.instantiateItem(container, position);
}
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
This is how I am setting up ViewPager.
private void setupViewPager(ViewPager viewPager) {
adapter = new ViewPagerFinder(getSupportFragmentManager());
for(int i = 0; i<numberOfPlans;i++){
adapter.addFragment(new OperatorPlanList(),"Plan");
//This is how I am setting up my fragments.
viewPager.setAdapter(adapter);
}
}
This is how I am setting up my Tabs.
private void setupTabIcons() {
System.out.println("number of plans ="+numberOfPlans);
for(int i = 0; i<numberOfPlans;i++){
HashMap<String,String> map = applist.get(i);
String planName = map.get("plan_name");
TextView tabOne = (TextView) LayoutInflater.from(this).inflate(R.layout.custom_tab, null);
tabOne.setText(planName);
tabLayout.getTabAt(i).setCustomView(tabOne);
}
}
This is how I am calling my methods.
viewPager = (ViewPager)findViewById(R.id.viewpager);
setupViewPager(viewPager);
tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(viewPager);
setupTabIcons();
Create an interface that all of the tab fragments will implement
public interface TabFragment {
void onTabSwiped(int position);
}
In the class that hosts the ViewPager have it implement ViewPager.OnPageChangeListener
Inside your ViewPagerFinder, during the instantiateItem() keep a list of TabFragment's (Create the fragment and cast it to the new interface, THEN call super).
Now in your Class that implements OnPageChangeListener, call the method onTabSwiped on all of your TabFragments passing in the position inside the overridden method onPageSelected(int position).
Now you will get a callback to each fragment during swiping, so you can check if its the correct position and perform whatever you need to do if it is the correct page.
There are ways to make this more efficient, but it's just a quick way to get it working. It will all be similar
-- You could also just get the fragment from your list inside your adapter during onPageSelected(int position), then call the interface method.
#Override
public void onPageSelected(int position){
pagerAdapter.mFragmentList.get(position).onTabSwiped();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_dictionary);
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
viewPager = (ViewPager) findViewById(R.id.viewpager);
setupViewPager(viewPager);
tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(viewPager);
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
setupNavMenu();
}
private void setupViewPager(ViewPager viewPager) {
adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.addFragment(new A(), "A");
adapter.addFragment(new B(), "B");
adapter.addFragment(new C(), "C");
adapter.addFragment(new D(), "D");
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);
}
}
The problem is:
When I starts the activity, i get the first fragment's view(A), BUT the next fragment(B) loads without the view, so when I slide to the next view(B), i get the view that i'v been loaded at the previous fragment, so now It's load fragment C...
the problem is that fragment B's content depends on the input in fragment A (data saved is sharedpreferences) so I allways get the default value at fragment B because it was loaded in fragment A.
The bug works the same way when I'm in the fragment D, so when I'll slide left to fragment C, it will show fragment C AND loads fragment B also.
its not a bug its the default functionality of a viewpager. for each nth page (n-1)th and (n+1)th page loads automatically with the nth page. I don't think there is any straight forward way to achieve what you want.
The best way would be to use a custom onSwipeListener instead of ViewPager and load the fragment yourself..
But for ViewPager you can use a hack:
After saving data in fragment A,
viewPager.setAdapter(adapter);
which will reload all fragments.
Don't forget to first save the current pager position.
after reset, set pager position so you don't go back to the first position every time.
not good coding but it should work.
on your ViewPagerAdapter. override method : getItemPosition : return POSITION_NONE
I have two fragments.
1) A fragment with just a TextView(OneFragment)
2) A fragment with a recycler view who's contents are being populated in the onCreateView() of the Fragment (Two Fragment)
In the Activity class I use a viewPager to control these fragments!!
However I'm faced with the problem.
The onCreateView() method of the TwoFragment is called when the activity is first loaded! When I go to the other fragments and return back to the Two Fragment the onCreateView() method is not called.
Instead of showing me a recyclerView it renders a blank screen ! However when I change my orientation of the device! The onCreateView() is called and and the list is populated!
I want it to show the TwoFragment while returning back to it.
However the OneFragment has no problems of this ! Any time I return to it, The normal big textView is shown!
Here is the Code for my ViewPager Adapter
class ViewPagerAdapter extends FragmentStatePagerAdapter {
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 getItemPosition(Object object){
// return PagerAdapter.POSITION_NONE;
Fragment f = (Fragment) object;
for(Fragment frag: mFragmentList){
return frag.getTagInt();
}
return FragmentStatePagerAdapter.POSITION_NONE;
}
#Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
public void clearAll(){
mFragmentList.clear();
mFragmentTitleList.clear();
notifyDataSetChanged();
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
And I set The ViewPager adapter like this :
private void setupViewPager(ViewPager viewPager,int choice) {
if(choice == 1){
ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.clearAll();
adapter.addFragment(new OneFragment(), "One");
adapter.addFragment(new TwoFragment(), "Two");
viewPager.setAdapter(adapter);
}
else{
ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.clearAll();
adapter.addFragment(new OneFragment(), "One");
adapter.addFragment(new OneFragment(), "Two");
viewPager.setAdapter(adapter);
}
}
and here's my previous getItemId()
#Override
public int getItemPosition(Object object){
// return PagerAdapter.POSITION_NONE;
Fragment f = (Fragment) object;
for(int i=0;i<getCount();i++){
Fragment fragment = getItem(i);
if(f.equals(fragment)){
return i;
}
}
return FragmentStatePagerAdapter.POSITION_NONE;
}
Any kind of help is Much Appreciated ! Cause I'm kinda stuck after going through the web and stackOverflow!!
You should not return POSITION_NONE in getItemPosition () for every request. POSITION_NONE means you're telling the pager adapter that this particular fragment is removed from the list and please modify the pages/fragments accordingly. When you return this, adapter will delete the fragment from its own private list of fragments. This is returned when you want to remove the fragment.
Send the position of the fragment using the indexof method of list interface.
See this post for more info.