I'm using nested FragmentStatePagerAdapter on my single activity application. The current layout is:
Activitiy
- Login Fragment
- Master Fragment
- Tab Fragment #1
- Tab Fragment #2
- Tab Fragment #3
- Another Fragment
- Another Fragment
Everytime i switch back from "Another Fragment" into "Master Fragment" the FragmentStatePagerAdapter instante the same item twice for example
First Time: Tab Fragment #1 #2 #3
Second Time: Tab Fragment #1 #1 #2 #2 #3 #3
Third Time: Tab Fragment #1 #1 #1 #2 #2 #2 #3 #3 #3.
To move from fragment to fragment i use
getSupportFragmentManager()
.beginTransaction()
.replace(android.R.id.content, XXXX.newInstance(query), TAG_XXX)
.addToBackStack(null)
.commit();
The MasterFragment code:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
initFragmentAdapter();
}
private void initFragmentAdapter() {
final FragmentSimpleAdapter adapter = new FragmentSimpleAdapter(getChildFragmentManager());
adapter.addFragment(
getString(...),
"...",
XXX.class);
...
ViewPager viewPager = (ViewPager) getActivity().findViewById(android.R.id.tabcontent);
viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(4);
PagerSlidingTabStrip viewTabs = (PagerSlidingTabStrip) getActivity().findViewById(
android.R.id.tabhost);
viewTabs.setViewPager(viewPager);
viewTabs.setOnPageChangeListener(adapter);
}
private final class FragmentSimpleAdapter extends FragmentStatePagerAdapter
implements ViewPager.OnPageChangeListener {
private final List<String> mTitles = new LinkedList<>();
private final List<String> mInstances = new LinkedList<>();
private final List<String> mTag = new LinkedList<>();
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;
public FragmentSimpleAdapter(FragmentManager fm) {
super(fm);
mFragmentManager = fm;
mFragmentTags = new HashMap<>();
}
#Override
public int getCount() {
return mTitles.size();
}
#Override
public CharSequence getPageTitle(int position) {
return mTitles.get(position);
}
#Override
public Fragment getItem(int position) {
return Fragment.instantiate(getActivity(), mInstances.get(position), null);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object obj = super.instantiateItem(container, position);
if (obj instanceof Fragment) {
Fragment f = (Fragment) obj;
String tag = f.getTag();
mFragmentTags.put(position, tag);
}
return obj;
}
public Fragment getFragment(int position) {
String tag = mFragmentTags.get(position);
if (tag == null) {
return null;
}
return mFragmentManager.findFragmentByTag(tag);
}
public FragmentSimpleAdapter addFragment(String title, String tag,
Class<? extends Fragment> fragment) {
mTitles.add(title);
mTag.add(tag);
mInstances.add(fragment.getName());
return this;
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
Analytics.sendViewAnalytic(mTag.get(position))
}
#Override
public void onPageScrollStateChanged(int state) {
}
}
problem:
PagerSlidingTabStrip viewTabs = (PagerSlidingTabStrip) getActivity().findViewById(
android.R.id.tabhost);
You are trying to re create and re create PagerSlidingTabStrip everytime you go back inside your Master's Fragment thus adding unnecessary fragment.
solution:
you need to check first if the tab already exist I dont really know about that PagerSlidingTabStrip I think it is a custom made class.
When you nest fragments inside fragments, you should instantiate the FragmentStatePagerAdapter using getChildFragmentManager and not getFragmentManager.
Fixed by moving the FragmentSimpleAdapter initialization code from onActivityCreate to onCreate
Related
I'm trying to start an animation when the fragment is showed on screen(I'm working with a ViewPager).
This a part of my code
public class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;
public ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm);
mFragmentManager = fm;
mFragmentTags = new HashMap<Integer, String>();
}
public ScreenSlidePageFragment getFragment(int position) {
String tag = mFragmentTags.get(position);
if (tag == null)
return null;
return (ScreenSlidePageFragment) mFragmentManager.findFragmentByTag(tag);
}
#Override
public Fragment getItem(int position) {
return ScreenSlidePageFragment.create(position);
}
#Override
public int getCount() {
return NUM_PAGES;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object obj = super.instantiateItem(container, position);
if (obj instanceof Fragment) {
// record the fragment tag here.
Fragment f = (Fragment) obj;
String tag = f.getTag();
mFragmentTags.put(position, tag);
System.out.println("holi");
}
return obj;
}
}
public class ScreenSlideActivity extends FragmentActivity {
(...)
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_screen_slide);
// Instantiate a ViewPager and a PagerAdapter.
mPager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new ScreenSlidePagerAdapter(getFragmentManager());
(...)
SimpleOnPageChangeListener mPageChangelistener = new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
// When changing pages, reset the action bar actions since they are dependent
// on which page is currently active. An alternative approach is to have each
// fragment expose actions itself (rather than the activity exposing actions),
// but for simplicity, the activity provides the actions in this sample.
ScreenSlidePageFragment currentFgm = mPagerAdapter.getFragment(position);
Animation animacion = AnimationUtils.loadAnimation(ScreenSlideActivity.this, R.anim.glasses);
currentFgm.getView().findViewById(R.id.image).startAnimation(animacion);
invalidateOptionsMenu();
}
(...)
The problem is when onPageSelected is called returns a NullPointer Exception in
currentFgm.getView().findViewById(R.id.image).startAnimation(animacion);
How could I start the animation when the fragment is showed on screen?
If you are trying to add animation to the ViewPager then you can implement the ViewPager.PageTransformer interface and supply it to the view pager to display a different animation than the default screen slide animation.
ViewPager mPager = (ViewPager) findViewById(R.id.pager);
...
mPager.setPageTransformer(true, new CustomTransformer());
You will find here two examples of PagerTransformers.
I'm using the v4 compatibility ViewPager in Android. My FragmentActivity has a bunch of data which is to be displayed in different ways on different pages in my ViewPager. So far I just have 3 instances of the same ListFragment, but in the future I will have 3 instances of different ListFragments. The ViewPager is on a vertical phone screen, the lists are not side-by-side.
Now a button on the ListFragment starts an separate full-page activity (via the FragmentActivity), which returns and FragmentActivity modifies the data, saves it, then attempts to update all views in its ViewPager. It is here, where I am stuck.
public class ProgressMainActivity extends FragmentActivity
{
MyAdapter mAdapter;
ViewPager mPager;
#Override
public void onCreate(Bundle savedInstanceState)
{
...
mAdapter = new MyAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.viewpager);
mPager.setAdapter(mAdapter);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
...
updateFragments();
...
}
public void updateFragments()
{
//Attempt 1:
//mAdapter.notifyDataSetChanged();
//mPager.setAdapter(mAdapter);
//Attempt 2:
//HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentById(mAdapter.fragId[0]);
//fragment.updateDisplay();
}
public static class MyAdapter extends FragmentPagerAdapter implements
TitleProvider
{
int[] fragId = {0,0,0,0,0};
public MyAdapter(FragmentManager fm)
{
super(fm);
}
#Override
public String getTitle(int position){
return titles[position];
}
#Override
public int getCount(){
return titles.length;
}
#Override
public Fragment getItem(int position)
{
Fragment frag = HomeListFragment.newInstance(position);
//Attempt 2:
//fragId[position] = frag.getId();
return frag;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE; //To make notifyDataSetChanged() do something
}
}
public class HomeListFragment extends ListFragment
{
...
public static HomeListFragment newInstance(int num)
{
HomeListFragment f = new HomeListFragment();
...
return f;
}
...
Now as you can see, my first attempt was to notifyDataSetChanged on the entire FragmentPagerAdapter, and this showed to update the data sometimes, but others I got an IllegalStateException: Can not perform this action after onSaveInstanceState.
My second attempt involed trying to call an update function in my ListFragment, but getId in getItem returned 0. As per the docs I tried by
acquiring a reference to the Fragment from FragmentManager, using
findFragmentById() or findFragmentByTag()
but I don't know the tag or id of my Fragments! I have an android:id="#+id/viewpager" for ViewPager, and a android:id="#android:id/list" for my ListView in the ListFragment layout, but I don't think these are useful.
So, how can I either:
a) update the entire ViewPager safely in one go (ideally returning the user to the page he was on before) - it is ok that the user see the view change.
Or preferably,
b) call a function in each affected ListFragment to update the ListView manually.
Any help would be gratefully accepted!
Barkside's answer works with FragmentPagerAdapter but doesn't work with FragmentStatePagerAdapter, because it doesn't set tags on fragments it passes to FragmentManager.
With FragmentStatePagerAdapter it seems we can get by, using its instantiateItem(ViewGroup container, int position) call. It returns reference to fragment at position position. If FragmentStatePagerAdapter already holds reference to fragment in question, instantiateItem just returns reference to that fragment, and doesn't call getItem() to instantiate it again.
So, suppose, I'm currently looking at fragment #50, and want to access fragment #49. Since they are close, there's a good chance the #49 will be already instantiated. So,
ViewPager pager = findViewById(R.id.viewpager);
FragmentStatePagerAdapter a = (FragmentStatePagerAdapter) pager.getAdapter();
MyFragment f49 = (MyFragment) a.instantiateItem(pager, 49)
OK, I think I've found a way to perform request b) in my own question so I'll share for others' benefit. The tag of fragments inside a ViewPager is in the form "android:switcher:VIEWPAGER_ID:INDEX", where VIEWPAGER_ID is the R.id.viewpager in XML layout, and INDEX is the position in the viewpager. So if the position is known (eg 0), I can perform in updateFragments():
HomeListFragment fragment =
(HomeListFragment) getSupportFragmentManager().findFragmentByTag(
"android:switcher:"+R.id.viewpager+":0");
if(fragment != null) // could be null if not instantiated yet
{
if(fragment.getView() != null)
{
// no need to call if fragment's onDestroyView()
//has since been called.
fragment.updateDisplay(); // do what updates are required
}
}
I've no idea if this is a valid way of doing it, but it'll do until something better is suggested.
Try to record the tag each time a Fragement is instantiated.
public class MPagerAdapter extends FragmentPagerAdapter {
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;
public MPagerAdapter(FragmentManager fm) {
super(fm);
mFragmentManager = fm;
mFragmentTags = new HashMap<Integer, String>();
}
#Override
public int getCount() {
return 10;
}
#Override
public Fragment getItem(int position) {
return Fragment.instantiate(mContext, AFragment.class.getName(), null);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object obj = super.instantiateItem(container, position);
if (obj instanceof Fragment) {
// record the fragment tag here.
Fragment f = (Fragment) obj;
String tag = f.getTag();
mFragmentTags.put(position, tag);
}
return obj;
}
public Fragment getFragment(int position) {
String tag = mFragmentTags.get(position);
if (tag == null)
return null;
return mFragmentManager.findFragmentByTag(tag);
}
}
If you ask me, the second solution on the below page, keeping track of all the "active" fragment pages, is better: http://tamsler.blogspot.nl/2011/11/android-viewpager-and-fragments-part-ii.html
The answer from barkside is too hacky for me.
you keep track of all the "active" fragment pages. In this case, you keep track of the fragment pages in the FragmentStatePagerAdapter, which is used by the ViewPager.
private final SparseArray<Fragment> mPageReferences = new SparseArray<Fragment>();
public Fragment getItem(int index) {
Fragment myFragment = MyFragment.newInstance();
mPageReferences.put(index, myFragment);
return myFragment;
}
To avoid keeping a reference to "inactive" fragment pages, we need to implement the FragmentStatePagerAdapter's destroyItem(...) method:
public void destroyItem(View container, int position, Object object) {
super.destroyItem(container, position, object);
mPageReferences.remove(position);
}
... and when you need to access the currently visible page, you then call:
int index = mViewPager.getCurrentItem();
MyAdapter adapter = ((MyAdapter)mViewPager.getAdapter());
MyFragment fragment = adapter.getFragment(index);
... where the MyAdapter's getFragment(int) method looks like this:
public MyFragment getFragment(int key) {
return mPageReferences.get(key);
}
"
Okay, after testing the method by #barkside above, I could not get it to work with my application. Then I remembered that the IOSched2012 app uses a viewpager as well, and that is where I found my solution. It does not use any fragment ID's or Tags as these are not stored by viewpager in an easily accessible way.
Here's the important parts from the IOSched apps HomeActivity. Pay particular attention to the comment, as therein lies the key.:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Since the pager fragments don't have known tags or IDs, the only way to persist the
// reference is to use putFragment/getFragment. Remember, we're not persisting the exact
// Fragment instance. This mechanism simply gives us a way to persist access to the
// 'current' fragment instance for the given fragment (which changes across orientation
// changes).
//
// The outcome of all this is that the "Refresh" menu button refreshes the stream across
// orientation changes.
if (mSocialStreamFragment != null) {
getSupportFragmentManager().putFragment(outState, "stream_fragment",
mSocialStreamFragment);
}
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (mSocialStreamFragment == null) {
mSocialStreamFragment = (SocialStreamFragment) getSupportFragmentManager()
.getFragment(savedInstanceState, "stream_fragment");
}
}
And store instances of you Fragments in the FragmentPagerAdapter like so:
private class HomePagerAdapter extends FragmentPagerAdapter {
public HomePagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return (mMyScheduleFragment = new MyScheduleFragment());
case 1:
return (mExploreFragment = new ExploreFragment());
case 2:
return (mSocialStreamFragment = new SocialStreamFragment());
}
return null;
}
Also, remember to guard your Fragment calls like so:
if (mSocialStreamFragment != null) {
mSocialStreamFragment.refresh();
}
You can copy FragmentPagerAdapter and modify some source code, add getTag() method
for example
public abstract class AppFragmentPagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentPagerAdapter";
private static final boolean DEBUG = false;
private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private Fragment mCurrentPrimaryItem = null;
public AppFragmentPagerAdapter(FragmentManager fm) {
mFragmentManager = fm;
}
public abstract Fragment getItem(int position);
#Override
public void startUpdate(ViewGroup container) {
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
String name = getTag(position);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
getTag(position));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
+ " v=" + ((Fragment) object).getView());
mCurTransaction.detach((Fragment) object);
}
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
mCurrentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
#Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitAllowingStateLoss();
mCurTransaction = null;
mFragmentManager.executePendingTransactions();
}
}
#Override
public boolean isViewFromObject(View view, Object object) {
return ((Fragment) object).getView() == view;
}
#Override
public Parcelable saveState() {
return null;
}
#Override
public void restoreState(Parcelable state, ClassLoader loader) {
}
public long getItemId(int position) {
return position;
}
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
protected abstract String getTag(int position);
}
then extend it, override these abstract method,don't need to be afraid of Android Group change
FragmentPageAdapter source code in the future
class TimeLinePagerAdapter extends AppFragmentPagerAdapter {
List<Fragment> list = new ArrayList<Fragment>();
public TimeLinePagerAdapter(FragmentManager fm) {
super(fm);
list.add(new FriendsTimeLineFragment());
list.add(new MentionsTimeLineFragment());
list.add(new CommentsTimeLineFragment());
}
public Fragment getItem(int position) {
return list.get(position);
}
#Override
protected String getTag(int position) {
List<String> tagList = new ArrayList<String>();
tagList.add(FriendsTimeLineFragment.class.getName());
tagList.add(MentionsTimeLineFragment.class.getName());
tagList.add(CommentsTimeLineFragment.class.getName());
return tagList.get(position);
}
#Override
public int getCount() {
return list.size();
}
}
Also works without problems:
somewhere in page fragment's layout:
<FrameLayout android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" android:id="#+id/fragment_reference">
<View android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone"/>
</FrameLayout>
in fragment's onCreateView():
...
View root = inflater.inflate(R.layout.fragment_page, container, false);
ViewGroup ref = (ViewGroup)root.findViewById(R.id.fragment_reference);
ref.setTag(this);
ref.getChildAt(0).setTag("fragment:" + pageIndex);
return root;
and method to return Fragment from ViewPager, if exists:
public Fragment getFragment(int pageIndex) {
View w = mViewPager.findViewWithTag("fragment:" + pageIndex);
if (w == null) return null;
View r = (View) w.getParent();
return (Fragment) r.getTag();
}
Alternatively you can override setPrimaryItem method from FragmentPagerAdapter like so:
public void setPrimaryItem(ViewGroup container, int position, Object object) {
if (mCurrentFragment != object) {
mCurrentFragment = (Fragment) object; //Keep reference to object
((MyInterface)mCurrentFragment).viewDidAppear();//Or call a method on the fragment
}
super.setPrimaryItem(container, position, object);
}
public Fragment getCurrentFragment(){
return mCurrentFragment;
}
I want to give my approach in case it can help anyone else:
This is my pager adapter:
public class CustomPagerAdapter extends FragmentStatePagerAdapter{
private Fragment[] fragments;
public CustomPagerAdapter(FragmentManager fm) {
super(fm);
fragments = new Fragment[]{
new FragmentA(),
new FragmentB()
};
}
#Override
public Fragment getItem(int arg0) {
return fragments[arg0];
}
#Override
public int getCount() {
return fragments.length;
}
}
In my activity I have:
public class MainActivity {
private ViewPager view_pager;
private CustomPagerAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
adapter = new CustomPagerAdapter(getSupportFragmentManager());
view_pager = (ViewPager) findViewById(R.id.pager);
view_pager.setAdapter(adapter);
view_pager.setOnPageChangeListener(this);
...
}
}
Then to get the current fragment what I do is:
int index = view_pager.getCurrentItem();
Fragment currentFragment = adapter.getItem(index);
This is my solution since I don't need to keep track of my tabs and need to refresh them all anyway.
Bundle b = new Bundle();
b.putInt(Constants.SharedPreferenceKeys.NUM_QUERY_DAYS,numQueryDays);
for(android.support.v4.app.Fragment f:getSupportFragmentManager().getFragments()){
if(f instanceof HomeTermFragment){
((HomeTermFragment) f).restartLoader(b);
}
}
Well, Consider I have have two fragments FragmentTab1 & ShowAllContactFragment. FragmentTab1 consists a list-view & and a button. When the button is clicked I replace ShowAllContactFragment in my viewpager. When shows ShowAllContactFragment, user can select several contacts by selecting check-box in a list-view & it also has a ADD contact button.
What I need: I want to update existing listview in FragmentTab1 , when user pressing ADD button in ShowAllContactFragment, after selecting some contacts in this list-view. I also remove ShowAllContactFragment, and show FragmentTab1 when this will occur.
My Solving Status: I follow the the android developers forum to communicate data between fragment via Activity. I'm almost done. I create Interface OnContactSelectedListener in ShowAllContactFragment & Implements in MainActivity. Everything is ok! . After debugging, I check my callback methods that I have data in MainActivity but I can't replace the ShowAllContactFragment & can't show the previous fragment FragmentTab1 & update it's list-view.
To open ShowAllContactFragment from FragmentTab1, I wrote like:
ShowAllContactFragment allContactsFragment = new ShowAllContactFragment();
FragmentTransaction transaction = getFragmentManager()
.beginTransaction();
transaction.addToBackStack(null);
transaction.add(R.id.fragmentTabLayout1, allContactsFragment);
transaction.commit();
My MainActivity & FragmentAdapter Looks :
public class MainActivity extends SherlockFragmentActivity implements
ShowAllContactFragment.OnContactSelectedListener {
ActionBar.Tab Tab1, Tab2, Tab3, Tab4;
private Context context = this;
// view pager
// Declare Variables
ActionBar actionBar;
ViewPager mPager;
Tab tab;
FragmentAdapter mAdapter;
List<Fragment> fragmentList = new ArrayList<Fragment>();
ArrayList<Person> blackListPersonList;
private final static String TAG_FRAGMENT = "TAG_FRAGMENT";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// set application in portrait mode
ActivityHelper.initialize(this);
actionBar = getSupportActionBar();
actionBar.setDisplayShowHomeEnabled(true);
actionBar.setDisplayShowTitleEnabled(true);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
addFragmentsInList();
// Locate ViewPager in activity_main.xml
mPager = (ViewPager) findViewById(R.id.pager);
// add an adapter to pager
mAdapter = new FragmentAdapter(getSupportFragmentManager(), mPager,
actionBar, fragmentList);
mPager.setAdapter(mAdapter);
addActionBarTabs();
}
public void addFragmentsInList() {
FragmentTab1 aFragmentTab1 = new FragmentTab1();
fragmentList.add(new FragmentTab1());
fragmentList.add(new FragmentTab2());
fragmentList.add(new FragmentTab3());
}
private void addActionBarTabs() {
String[] tabs = { "Tab 1", "Tab 2", "Tab 3" };
for (String tabTitle : tabs) {
ActionBar.Tab tab = actionBar.newTab().setText(tabTitle)
.setTabListener(tabListener);
actionBar.addTab(tab);
}
}
private ActionBar.TabListener tabListener = new ActionBar.TabListener() {
#Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
mPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
#Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
};
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// add action menu here
MenuInflater inflater = getSupportMenuInflater();
inflater.inflate(R.menu.activity_itemlist, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.add_item:
// openSearch();
Toast.makeText(context, " add_item ", Toast.LENGTH_SHORT).show();
return true;
case R.id.about:
// composeMessage();
Toast.makeText(context, " about", Toast.LENGTH_SHORT).show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
class FragmentAdapter extends FragmentPagerAdapter implements
ViewPager.OnPageChangeListener {
private ViewPager mViewPager;
final int TOTAL_PAGES = 3;
private List<Fragment> fragments;
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
public FragmentAdapter(FragmentManager fm, ViewPager pager,
ActionBar actionBar, List<Fragment> fragmentsList) {
super(fm);
this.mViewPager = pager;
this.mViewPager.setOnPageChangeListener(this);
this.fragments = fragmentsList;
}
#Override
public Fragment getItem(int position) {
// switch (position) {
// case 0:
// return FragmentTab1.newInstance();
// case 1:
// return FragmentTab2.newInstance();
// case 2:
// return FragmentTab3.newInstance();
// default:
// throw new IllegalArgumentException(
// "The item position should be less or equal to:"
// + TOTAL_PAGES);
// }
return this.fragments.get(position);
}
#Override
public int getCount() {
// return TOTAL_PAGES;
return this.fragments.size();
}
public ViewPager getViewPager() {
return mViewPager;
}
// added newly
#Override
public Object instantiateItem(ViewGroup container, int position) {
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);
}
#Override
public void onPageScrollStateChanged(int arg0) {
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
#Override
public void onPageSelected(int position) {
actionBar.setSelectedNavigationItem(position);
}
}
#Override
public void onBackPressed() {
Log.e(TAG_FRAGMENT, "onBackPressed");
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
Log.i("MainActivity", "popping backstack");
fm.popBackStack();
} else {
Log.i("MainActivity", "nothing on backstack, calling super");
super.onBackPressed();
}
}
#Override
public void onContactSelected(ArrayList<Person> contactNumberlist) {
// data comes here, no problem.
this.blackListPersonList = contactNumberlist;
Log.d("onContactSelected", "onContactSelected");
// get error here
FragmentTab1 aFragmentTab1 = (FragmentTab1) mAdapter.getItem(0);
if (aFragmentTab1 != null) {
aFragmentTab1.updateFragment1(blackListPersonList);
}
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.add(R.id.fragment_all_contacts_layout, aFragmentTab1);
transaction.commit();
}
public ArrayList<Person> getBlackListContacts() {
return blackListPersonList;
}
// public Fragment getFragment(ViewPager pager){
// Fragment theFragment = fragments.get(pager.getCurrentItem());
// return theFragment;
// }
}
FrgmentTab1 looks :
public class FragmentTab1 extends SherlockFragment implements OnClickListener {
Button btnTest;
ViewPager pager;
LinearLayout layoutBlockNumbers;
LinearLayout layoutContact, layoutCallLog, layoutSMSLog, layoutManually;
public Context mContext;
CustomizedDialog dialog;
private static final int CONTACT_PICKER_RESULT = 1001;
private static final String DEBUG_TAG = "Contact List";
private static final double RESULT_OK = -1;
ListView listViewOnlyBlackListNumber;
ArrayList<Person> personList;
BlackListAdapter aBlackListAdapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragmenttab1, container,
false);
layoutBlockNumbers = (LinearLayout) rootView
.findViewById(R.id.layoutAddBlockNumbers);
layoutBlockNumbers.setOnClickListener(this);
listViewOnlyBlackListNumber = (ListView) rootView
.findViewById(R.id.listViewOnlyBlackListNumber);
personList = ((MainActivity) getActivity()).getBlackListContacts();
if (personList != null) {
aBlackListAdapter = new BlackListAdapter(getActivity(), personList,
m_onSelectedEventCalender);
listViewOnlyBlackListNumber.setAdapter(aBlackListAdapter);
} else {
listViewOnlyBlackListNumber.setEmptyView(container);
}
return rootView;
}
public void updateFragment1(ArrayList<Person> personList) {
// trying to update when came back here
aBlackListAdapter = new BlackListAdapter(getActivity(), personList,
m_onSelectedEventCalender);
listViewOnlyBlackListNumber.setAdapter(aBlackListAdapter);
aBlackListAdapter.notifyDataSetChanged();
}
}
Get Error In onContactSelected, inside MainActivity
10-30 00:22:29.674: E/AndroidRuntime(26834): FATAL EXCEPTION: main
java.lang.IllegalStateException: Can't change container ID of fragment FragmentTab1{42d27380 #0 id=0x7f040032 android:switcher:2130968626:0}: was 2130968626 now 2130968638
E/AndroidRuntime(26834): at android.support.v4.app.BackStackRecord.doAddOp(BackStackRecord.java:407)
E/AndroidRuntime(26834): at android.support.v4.app.BackStackRecord.add(BackStackRecord.java:384)
E/AndroidRuntime(26834): at com.mobigic.callblocker.MainActivity.onContactSelected(MainActivity.java:240)
Hope, Somebody help me.
Your question is not very clear especially the part about how you handle those fragments. Like when you're showing the ShowAllContactFragment fragment in which layout do you put it? From the code you posted it seems you're blindly adding the ShowAllContactFragment fragment directly in the layout containing the ViewPager which isn't right.
Related to the error, you get a reference to one of the fragments already managed by the FragmentManager through the adapter of the ViewPager and then you retry to add it in a transaction, action which will fail. If you're trying to show the previous shown FragmentTab1 fragment, after showing and working with the ShowAllContactFragment fragment, you should try simply removing the last recorded action of the FragmentManager through one of its popBackStack...() methods.
Edit: I looked at your code but what you're doing is still ambiguous. I've looked at the layout for the main activity but you don't have a container with the id R.id.fragment_all_contacts_layout so I'm assuming you're somehow trying to insert the new FragmentTab1 in the layout of the old FragmentTab1 which really doesn't make any sense(not to mention you add the ShowAllContactFragment to a container with the id R.id.fragmentTabLayout1 which I also can't see). Anyway, I'm assuming that you want the ShowAllContactFragment to replace the FragmentTab1 from the ViewPager. For this you'll need a wrapper fragment to hold the two nested fragment and also enable the communication between them. For example the wrapper fragment:
public static class WrapperFragment extends Fragment implements
OnContactSelectedListener {
private static final String TAG_FIRST = "tag_first";
private static final String TAG_SECOND = "tag_second";
private static final int CONTENT_ID = 1000;
private FragmentTab1 mFrag1;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
FrameLayout content = new FrameLayout(getActivity());
content.setId(CONTENT_ID);
if (getChildFragmentManager().findFragmentByTag(TAG_SECOND) == null) {
mFrag1 = (FragmentTab1) getChildFragmentManager()
.findFragmentByTag(TAG_FIRST);
if (mFrag1 == null) {
mFrag1 = new FragmentTab1();
getChildFragmentManager().beginTransaction()
.add(CONTENT_ID, mFrag1, TAG_FIRST).commit();
}
}
return content;
}
#Override
public void onContactSelected(List<Person> contactNumberlist) {
getChildFragmentManager().popBackStackImmediate();
mFrag1.updateFragment1(contactNumberlist);
}
public void showPickerfragment() {
getChildFragmentManager().beginTransaction()
.replace(CONTENT_ID, new ShowAllContactFragment())
.addToBackStack(null).commit();
}
}
This will be the fragment that you'll use in the ViewPager's adapter instead of the FragmentTab1 fragment. Notice that it implements the OnContactSelectedListener interface. You'll also need to make some changes to other parts of the code:
#Override
public void onClick(View v) {
if (v == layoutCallLog) {
dialog.dismiss();
// make the wrapper fragment to open the ShowAllContactFragment fragment
((WrapperFragment) getParentFragment()).showPickerfragment();
// rest of the code
The ShowAllContactFragment will need to be modified to send the selection event to the wrapper fragment which implements its interface:
#Override
public void onClick(View v) {
if (v == btnAdd) {
Toast.makeText(getActivity(), "" + blockListedPersonList.size(),
Toast.LENGTH_SHORT).show();
((OnContactSelectedListener) getParentFragment())
.onContactSelected(blockListedPersonList);
}
}
And in the main activity to handle the BACK button when the ShowAllContactFragment is showing in the ViewPager:
#Override
public void onBackPressed() {
if (mViewPager.getCurrentItem() == 0) { // I assumed 0 is the position in the adapter where the WrapperFragment will be used
Fragment targetPage = getSupportFragmentManager()
.findFragmentByTag("android:switcher:" + PAGER_ID + ":" + 0); // PAGER_ID is the id of the ViewPager
if (targetPage.getChildFragmentManager().getBackStackEntryCount() > 0) {
targetPage.getChildFragmentManager().popBackStack();
}
return;
}
super.onBackPressed();
}
Keep in mind that you'll need to save the data of the nested fragments.
I used FragmentActivity and ViewPager in differents projects and there are two solutions:
1: You can use onHiddenChanged(boolean hidden) method on your FragmentTab1.
override fun onHiddenChanged(hidden: Boolean) {
// TODO Auto-generated method stub
if (!hidden) {
// check if personList size is changed
// and then call updateFragment1() methode
}
super.onHiddenChanged(hidden)
}
2: Use a static method: you can ake updateFragment1() method static so when user pressing "ADD", call updateFragment1().
Hope it helps.
I've looked at so many questions here that I don't even know exactly what I'm looking for.
I have a simple app that uses a ViewPager. It has 3 tabs and there is a fragment for each tab. The first fragment contains a ListView. I want to be able to click on an element in the ListView and have that bring me to a different fragment.
So basically I want to remove the fragment that contained the ListView once an element is clicked and add in a new fragment. I've tried to do this in a few different ways with none working.
The last thing I tried was to edit the TabsPageAdapter once an element was clicked which pretty much works except when I press the back button it exits the app. Also it doesn't seem like the cleanest way of doing this.
TabsPagerAdapter
public class TabsPagerAdapter extends FragmentStatePagerAdapter {
SherlockFragment mf;
TalkingPointsFragment tpf;
ContactFragment gf;
int mode = 0;
public TabsPagerAdapter(FragmentManager fm) {
super(fm);
mf = new CanvassFragment();
tpf = new TalkingPointsFragment();
gf = new ContactFragment();
}
public TabsPagerAdapter(FragmentManager fm, int mode)
{
super(fm);
if(mode == 0)
{
mf = new CanvassFragment();
tpf = new TalkingPointsFragment();
gf = new ContactFragment();
}
else if(mode == 1)
{
mf = new ContactFragment();
tpf = new TalkingPointsFragment();
gf = new ContactFragment();
}
}
#Override
public SherlockFragment getItem(int index) {
switch (index) {
case 0:
return mf;
case 1:
return tpf;
case 2:
return gf;
}
return null;
}
#Override
public int getCount() {
// get item count - equal to number of tabs
return 3;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object)
{
}
}
The onclick code:
ViewPager viewp = (ViewPager) getActivity().findViewById(R.id.pager);
TabsPagerAdapter mAdapter = new TabsPagerAdapter(getActivity().getSupportFragmentManager(),1);
viewp.setAdapter(mAdapter);
mAdapter.notifyDataSetChanged();
layout_main.xml
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:actionBarTabStyle="#drawable/actionbar_tab_indicator">
I FragmentStatePagerAdapter too and when a user selects map from the ActionBar, I add the GoogleMapsFragment on top of the FragmentStatePagerAdapter:
// create a new map
mapsFragment = GoogleMapFragment.newInstance();
// Then we add it using a FragmentTransaction.
FragmentTransaction fragmentTransaction = getSupportFragmentManager()
.beginTransaction();
fragmentTransaction.add(android.R.id.content, mapsFragment,
FRAGMENT_MAP_TAG);
fragmentTransaction.commit();
For your case you would probably need to add it to the backstack too, which I have not because in my app the user has to navigate back using the ActionBar.
Thought this approach could work for you too when a user selects an item from your list.
Of course this has the disadvantage of not being able to use the FragmentStatePagerAdapter until the user navigates back. So I am not sure wether that would be acceptable for your app.
Ok, so this took a bit more code that I imagined. Hope you get the idea:
public class MainClass extends FragmentActivity implements CanvassCallback {
// save a single reference to ViewPager and TabsPagerAdapter
private ViewPager mViewPager;
private TabsPagerAdapter mAdapter;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.mViewPager = (ViewPager) findViewById(R.id.pager);
this.mAdapter = new TabsPagerAdapter(getSupportFragmentManager(), this);
mViewPager.setAdapter(mAdapter);
...
}
// from the CanvassCallback interface
public void itemSelected() {
mAdapter.canvassSelected();
mAdapter.notifyDataSetChanged();
}
#Override
public void onBackPressed() {
if (mViewPager.getCurrentItem() == 0 && mAdapter.isCanvassSelected() {
mAdapter.canvassSelected();
mAdapter.notifyDataSetChanged();
}
else {
super.onBackPressed();
}
}
}
Mockup of your CanvassFragment showing the callback
public class CanvassFragment extends SherlockFragment {
public interface CanvassCallback {
public void itemSelected();
}
private CanvassCallback canvassCallback;
public void setCanvassCallback(CanvassCallback canvassCallback) {
this.canvassCallback = canvassCallback;
}
...
// The onClick of your item
private void onClick() {
// notify your activity that an item was selected
canvassCallback.itemSelected();
}
}
The registeredFragments are not strictly needed but I think it provides some value if your need to call methoods on your Fragment from activity.
public class TabsPagerAdapter extends FragmentStatePagerAdapter {
// see upvoted answer from http://stackoverflow.com/questions/8785221/retrieve-a-fragment-from-a-viewpager
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
private boolean canvassSelected = false;
private CanvassCallback canvassCallback;
public TabsPagerAdapter(FragmentManager fm, CanvassCallback canvassCallback) {
super(fm);
this.canvassCallback = canvassCallback;
}
public void canvassSelected() {
canvassSelected = !canvassSelected;
}
public boolean isCanvassSelected() {
return canvassSelected;
}
#Override
public SherlockFragment getItem(int index) {
switch (index) {
case 0:
if (canvassSelected)
return new ContactFragment();
CanvassFragment canvassFragment = new CanvassFragment();
// this ensures that your Activity gets notified when an item is clicked
canvassFragment.setCanvassCallback(canvassCallback);
return canvassFragment;
case 1:
return new TalkingPointsFragment();
case 2:
return new ContactFragment();
}
return null;
}
#Override
public int getCount() {
// get item count - equal to number of tabs
return 3;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Log.d(TAG, "instantiateItem " + position);
Fragment fragment = (Fragment) super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
Log.d(TAG, "destroyItem " + position);
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
}
I have a FragmentList with some items. If I click an item the appropriate Fragment will open which contains a Viewpager. No problem so far but if I go back to the List and click the item again the method getItem(int position) is not called anymore.
Here a link with a similar problem:
Display fragment viewpager within a fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.activity_fragment_runtime, container, false);
mViewPager = (ViewPager) view.findViewById(R.id.pager);
indicator = (TitlePageIndicator) view.findViewById(R.id.indicator);
MyFragmentPagerAdapter mMyFragmentPagerAdapter = new MyFragmentPagerAdapter(getActivity().getSupportFragmentManager());
mViewPager.setAdapter(mMyFragmentPagerAdapter);
indicator.setViewPager(mViewPager);
return view;
}
class MyFragmentPagerAdapter extends FragmentStatePagerAdapter implements TitleProvider {
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public String getTitle(int position) {
return muxbusTitles[position];
}
#Override
public Fragment getItem(int location) {
return muxbusFragmentList.get(location);
}
#Override
public int getCount() {
return muxbusTitles.length;
}
}
return view;
}
Edit:
If someone is interested,the solution was to extends from PagerAdapter instead of FragmentStatePagerAdpater because the Viewpager is already in a Fragment..
I did have similar problem and after scratching my head for quite some time i made an my own understanding (assumption) that ViewPager is keeping the reference of its child fragments into memory even the hosted Fragment or Activity is destroyed (didn't tested with Activity). So i wrote this method freeFragmentsFromViewPager(). You need to call it from onDestroy() of your Fragment.
/**
* This method free the child fragments from ViewPager as ViewPager keeps the reference
* of child fragments into memory and next time when you again come to visit view pager it won't show the fragment layout
* because it has the reference to the fragment whilst the fragment's view was already destroyed.
*/
private void freeFragmentsFromViewPager()
{
for(int i = 0; i < TabsFragmentPagerAdapter.fragments.length; i++)
{
Fragment fragment = TabsFragmentPagerAdapter.fragments[i];
if(fragment != null)
{
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.remove(fragment);
fragmentTransaction.commit();
TabsFragmentPagerAdapter.fragments[i] = null;
}
}
}
And this is my Adapter code
public class TabsFragmentPagerAdapter extends FragmentPagerAdapter
{
public static final int NUM_TABS = 3;
public static Fragment[] fragments = new Fragment[NUM_TABS];
public TabsFragmentPagerAdapter(FragmentManager fm)
{
super(fm);
}
#Override
public Fragment getItem(int position)
{
Log.e("TabsFragmentPagerAdapter", "getItem");
Fragment fragment = TabsFragmentFactory.newInstace(position);
fragments[position] = fragment;
return fragment;
}
#Override
public int getCount()
{
return NUM_TABS;
}
#Override
public CharSequence getPageTitle(int position)
{
return TabsFragmentFactory.getName(position);
}