I've always worked with FragmentActivity and TabHost to add/remove/replace Fragment programmatically.
What I was using was:
public void addFragments(String tabName, Fragment fragment,
boolean animate, boolean add, Bundle bundle) {
currentSelectedTab = tabName;
if (add) {
hMapTabs.get(tabName).add(fragment);
}
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
if (animate) {
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
}
if (bundle!=null){
fragment.setArguments(bundle);
}
ft.replace(R.id.realtabcontent, fragment, tabName);
ft.commitAllowingStateLoss();
}
public void removeFragment() {
Fragment fragment = hMapTabs.get(currentSelectedTab).get(
hMapTabs.get(currentSelectedTab).size() - 2);
hMapTabs.get(currentSelectedTab).remove(
hMapTabs.get(currentSelectedTab).size() - 1);
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
ft.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right);
ft.replace(R.id.realtabcontent, fragment);
ft.commitAllowingStateLoss();
}
With that, everything works OK.
Now I'm trying to manage this Fragment's behavior on a page of my ViewPager.
I've been searching on StackOverFlow for three hours and I get confused everytime more and more. Tried with public functions on FragmentPagerAdapter for example.
And what only worked makes me a white blank screen on adding Fragments.
How would you do it?
Thanks in advance.
I found a workaround for my problem but I don't know if it's effective.
I'm defining a HashMap to keep my Fragments:
private HashMap<Integer, ArrayList<Fragment>> hMapTabs = new HashMap<Integer, ArrayList<Fragment>>();
then I initializate it and I add my first fragments of every position (in this case, the fragments are the same).
hMapTabs.put(0, new ArrayList<Fragment>());
hMapTabs.put(1, new ArrayList<Fragment>());
hMapTabs.put(2, new ArrayList<Fragment>());
hMapTabs.get(0).add(new PrimerFragment());
hMapTabs.get(1).add(new PrimerFragment());
hMapTabs.get(2).add(new PrimerFragment());
and I set my adapter to my ViewPager. This would be the FragmentAdapterPager:
public class CollectionPagerAdapter extends FragmentStatePagerAdapter {
final int NUM_ITEMS = 3; // number of tabs
FragmentManager fm;
HashMap<Integer, ArrayList<Fragment>> hMapTabs;
public CollectionPagerAdapter(FragmentManager fm, HashMap<Integer, ArrayList<Fragment>> hMapTabs) {
super(fm);
this.fm = fm;
this.hMapTabs = hMapTabs;
}
#Override
public Fragment getItem(int position) {
return hMapTabs.get(position).get(hMapTabs.get(position).size()-1);
}
#Override
public int getCount() {
return NUM_ITEMS;
}
#Override
public CharSequence getPageTitle(int position) {
String tabLabel = null;
switch (position) {
case 0:
tabLabel = "tab1";
break;
case 1:
tabLabel = "tab2";
break;
case 2:
tabLabel = "tab3";
break;
}
return tabLabel;
}
}
from every fragment, I can add or remove fragments with the functions:
public void addFragment(int position, Fragment frag) {
hMapTabs.get(position).add(frag);
mCollectionPagerAdapter = new CollectionPagerAdapter(
getSupportFragmentManager(), hMapTabs);
mViewPager.setAdapter(mCollectionPagerAdapter);
}
public void removeFragment(int position) {
hMapTabs.get(position).remove(
hMapTabs.get(position).size() - 1);
mCollectionPagerAdapter = new CollectionPagerAdapter(
getSupportFragmentManager(), hMapTabs);
mViewPager.setAdapter(mCollectionPagerAdapter);
}
#Override
public void onBackPressed() {
int position = mViewPager.getCurrentItem();
if (hMapTabs.get(position).size() <= 1) {
super.onBackPressed();
} else {
removeFragment(position);
}
}
but what I can't do is adding an Animation... how would I do it? :D
Related
I spent the last days searching for a way to communicate two fragments, there are tons of answers and tutorials about how to do it but none of them worked for me. I tried parcelable, boundle, interfaces but nothing worked (probably because I made a mistake implementing them).
The tricky thing is that most tutorials explain a basic case with two fragments with IDs in their layouts but my app has a more complex architecture. Check the image below:
The aim is to send a custom object from the ListView (lv1) to ListView (lv2). Each listview is in a fragment created using FragmentPagerAdapter and belong to a different Drawer Fragment as explained in the image.
Can anyone point me in the right direction here?
MainActivity code of how Navigation Drawer fragments are created:
#Override
public void onDrawerItemSelected(View view, int position) {
displayView(position);
}
private void displayView(int position) {
String title = getString(R.string.app_name);
HomeFragment homeFragment = null;
QuotesFragment quotesFragment = null;
WatchlistFragment watchlistFragment = null;
switch (position) {
case 0:
homeFragment = new HomeFragment();
title = getString(R.string.title_inicio);
break;
case 1:
quotesFragment = new QuotesFragment();
title = getString(R.string.title_cotizaciones);
break;
case 2:
watchlistFragment = new WatchlistFragment();
title = getString(R.string.title_messages);
break;
}
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
switch (position) {
case 0:
fragmentTransaction.replace(R.id.container_body, homeFragment, "HomeFragment");
break;
case 1:
fragmentTransaction.replace(R.id.container_body, quotesFragment, "QuotesFragment");
break;
case 2:
fragmentTransaction.replace(R.id.container_body, watchlistFragment, "WatchlistFragment");
break;
}
Also, check below the code of how fragmentpageadapter creates the fragmenttabs:
private void setupViewPager(ViewPager viewPager) {
ViewPagerAdapter adapter = new ViewPagerAdapter(getChildFragmentManager());
adapter.addFragment(new QuotesIndexespage(),getResources().getString(R.string.one));
adapter.addFragment(new QuotesCurrenciesPage(),getResources().getString(R.string.two));
adapter.addFragment(new QuotesCommoditiesPage(),getResources().getString(R.string.three));
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);
}
}
I have a navigation drawer with menu HOME, SETTING, ABOUT US etc on a fragment. And only HOME has a swipe tab. On the start of the app, Home is opened, but after that whenever i click home again it shows error saying Home Fragment is not attached to Activity.
my code is as follows
Drawer.java
which is mainAciticity
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
myDrawerLayout.closeDrawer(drawerBody);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment fragment;
switch (position){
case 0:
fragment = new Home();
fragmentTransaction.replace(R.id.main_content, fragment);
break;
case 1:
fragment = new Settings();
fragmentTransaction.replace(R.id.main_content, fragment);
break;
case 2:
fragment = new Aboutus();
fragmentTransaction.replace(R.id.main_content, fragment);
break;
case 3:
fragment = new Help_and_Feedback();
fragmentTransaction.replace(R.id.main_content, fragment);
break;
case 4:
fragment = new Fill_up_form();
fragmentTransaction.replace(R.id.main_content, fragment);
break;
}
setTitle(position);
fragmentTransaction.commit();
}
Home.java
public class Home extends Fragment implements ActionBar.TabListener {
ViewPager myViewPager;
ActionBar myActionBar;
MyFragmentPagerAdapter myFragmentPagerAdapter;
String[] selectedTabImage;
String[] unselectedTabImage;
String[] BloodGroups;
public Home() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.home, container, false);
//Initialization
myViewPager = (ViewPager) v.findViewById(R.id.viewpager);
myFragmentPagerAdapter = new MyFragmentPagerAdapter(getActivity().getSupportFragmentManager());
//viewpager work
myViewPager.setAdapter(myFragmentPagerAdapter);
myViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int i, float v, int i2) {
}
#Override
public void onPageSelected(int i) {
myActionBar.setSelectedNavigationItem(i);
}
#Override
public void onPageScrollStateChanged(int i) {
}
});
myActionBar =((ActionBarActivity)getActivity()).getSupportActionBar();
myActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
//get the array of images for tab
selectedTabImage = getResources().getStringArray(R.array.tab_selected);
unselectedTabImage = getResources().getStringArray(R.array.tab_unselected);
BloodGroups = getResources().getStringArray(R.array.blood_groups);
//Add Tabs
for (int i = 0; i < myFragmentPagerAdapter.getCount(); i++) {
String name = selectedTabImage[i];
int tabId = getResources().getIdentifier(name,"drawable", getActivity().getPackageName());
myActionBar.addTab(myActionBar.newTab().setIcon(tabId).setTabListener(this));
}
return v;
}
#Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
int pos = tab.getPosition();
myViewPager.setCurrentItem(pos);
String name = selectedTabImage[pos];
int tabId = getResources().getIdentifier(name,"drawable", getActivity().getPackageName());
tab.setIcon(tabId);
}
#Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
String name = selectedTabImage[tab.getPosition()];
int tabId = getResources().getIdentifier(name,"drawable", getActivity().getPackageName());
tab.setIcon(tabId);
}
#Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
}
public class MyFragmentPagerAdapter extends FragmentPagerAdapter {
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Fragment fragment = new DonarList();
Bundle args = new Bundle();
args.putInt("bloodGroupPosition", position);
fragment.setArguments(args);
return fragment;
}
#Override
public int getCount() {
return 8;
}
}
#Override
public void onStop() {
super.onStop();
myActionBar.setNavigationMode(0);
}
}
the error stacktrace
05-29 14:44:31.961 12034-12034/com.example.p6.bloodies E/AndroidRuntime﹕ FATAL EXCEPTION: main
java.lang.IllegalStateException: Fragment Home{4120fd50} not attached to Activity
at android.support.v4.app.Fragment.getResources(Fragment.java:619)
at com.example.p6.bloodies.Home.onTabSelected(Home.java:114)
at android.support.v7.internal.app.WindowDecorActionBar.selectTab(WindowDecorActionBar.java:634)
at android.support.v7.internal.app.WindowDecorActionBar.setSelectedNavigationItem(WindowDecorActionBar.java:414)
at android.support.v7.internal.app.WindowDecorActionBar.setNavigationMode(WindowDecorActionBar.java:1297)
at com.example.p6.bloodies.Home.onCreateView(Home.java:72)
at android.support.v4.app.Fragment.performCreateView(Fragment.java:1786)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:947)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1126)
at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:739)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1489)
at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:454)
at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4424)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:817)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:584)
at dalvik.system.NativeStart.main(Native Method)
The code in line 72
myActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
The code in line 114
int tabId = getActivity().getResources().getIdentifier(name,"drawable", getActivity().getPackageName());
Not really clear and Cannot see your full activity code but Possibly your activity is still doing some (background) process or something when the code for the fragment runs. try moving the code to onActivityCreated() in the fragment life cycle which by then the fragment has been attached to the activity. Also good to check if fragment is already added in your code like.
if(!isAdded()) {
return;
}
Remove current fragment by this method may solve your issue
public void removeFragment(Fragment fragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.remove(fragment);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
transaction.commit();
}
I want to display another Fragment when the phone isn't connected to a WiFi network. Normally I'm displaying MainFragment, when no connection is available ErrorFragment.
I'm using mWifi.isConnected() to check if there is a connection.
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
if (mWifi.isConnected()) {
new PostTask(1).execute();
} else {
MainFragment.firstPageListener.onSwitchToNextFragment();
}
}
This is my code (inside MainFragment) to switch to the ErrorFragment, but it's giving me
11-23 20:57:29.588: E/AndroidRuntime(4025): java.lang.IllegalStateException: Recursive entry to executePendingTransactions
This is in my MainActivity (I'm using a ViewPager):
public class SectionsPagerAdapter extends FragmentPagerAdapter {
private final class FirstPageListener implements FirstPageFragmentListener {
public void onSwitchToNextFragment() {
mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();
if (mFragmentAtPos0 instanceof MainFragment) {
mFragmentAtPos0 = new ErrorFragment(listener);
} else { // Instance of NextFragment
mFragmentAtPos0 = new MainFragment(listener);
}
notifyDataSetChanged();
}
}
private String[] titles = { "SEARCH", "LIST" };
private final FragmentManager mFragmentManager;
public Fragment mFragmentAtPos0;
private Context context;
FirstPageListener listener = new FirstPageListener();
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
mFragmentManager = fm;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0: // Fragment # 0
if (mFragmentAtPos0 == null) {
mFragmentAtPos0 = new MainFragment(listener);
}
return mFragmentAtPos0;
case 1: // Fragment # 1
return new ListFragment();
}
return null;
}
#Override
public int getCount() {
return titles.length;
}
#Override
public int getItemPosition(Object object) {
if (object instanceof MainFragment && mFragmentAtPos0 instanceof ErrorFragment) {
return POSITION_NONE;
}
if (object instanceof MainFragment && mFragmentAtPos0 instanceof MainFragment) {
return POSITION_NONE;
}
return POSITION_UNCHANGED;
}
#Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
}
public interface MainFragmentListener {
void onSwitchToNextFragment();
}
I tried to change Fragments with:
FragmentManager fragmentManager2 = getFragmentManager();
FragmentTransaction fragmentTransaction2 = fragmentManager2.beginTransaction();
ErrorFragment fragment2 = new ErrorFragment();
fragmentTransaction2.addToBackStack("xyz");
fragmentTransaction2.hide(MainFragment.this);
fragmentTransaction2.add(android.R.id.content, fragment2);
fragmentTransaction2.commit();
But when I swipe to the other Tab, the Fragments are overlapping... What should do? Thanks!
(The thumbs down is the ErrorFragment, the Button and On/Off Switch, is in ListFragment)
Try to remove
fragmentTransaction2.hide(MainFragment.this);
and change
fragmentTransaction2.add(android.R.id.content, fragment2);
to
fragmentTransaction2.replace(android.R.id.content, fragment2);
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'm using a Viewpager with a the FragmentPagerAdapter to allow adding and removing of pages. Each page displays data obtained from the internet.
As a new page is added, a new Fragment is associated with that page. The data are obtained via AsyncTask and displayed in the Fragment. When the user chooses to remove a page, the idea is to destroy the page and the associated Fragment.
In general, this all works well. The problem I'm seeing is as follows:
You have three pages with data:
[Page 1] [Page 2] [Page 3]
You delete any page other than the last one, say page 2; Page 2 disappears as desired:
[Page 1] [Page 3]
You add a new page; but instead of a blank, new page, the new page shows the data (view) from page 3.
[Page 1] [Page 3] [Page 4, but showing view/data of Page 3, should be blank]
The page removal code in my activity is as follows:
// Destroy fragment for this page
DataListFragment curFrag = getFragmentByName(currentPage);
FragmentManager fm = getSupportFragmentManager();
fm.beginTransaction().remove(curFrag).commit();
fm.executePendingTransactions();
curFrag = null;
// Remove page and update adapter
mPageTitles.remove(position);
mAdapter.notifyDataSetChanged();
Using the debugger, it shows that the fragment is removed from the FragmentManager after the executePendingTransactions() call. But in the FrampePagerAdapters call, mAdapter.notifyDataSetChanged(), the fragment is added back in and then displayed when a new page is created.
I tried using the FrameStatePagerAdapter, since that should allow destroying fragments, but it did not work. In my FragmentPagerAdapter's getItemPosition() method, I use return FragmentAdapter.POSITION_NONE; as pointed out in another SO article I came across.
It seems as if the View for that page is not destroyed, but then added back into the new page. I tried using the removeViewAt() method on the view of the new page, but that did not work.
Being new to this, I'm sure I'm missing something obvious...
You should rather extend FragmentStatePagerAdapter and remove corresponding item from the list of items in that adapter, instead of trying to remove a Fragment from activity. Do not forget to call adapter.notifyDataSetChanged() after you removed an item in adapter. Once done, ViewPager and FragmentStatePagerAdapter will take care for the rest.
See your getCount() method is returning exact number of items in the viewPager. And yes, FragmentStatePagerAdapter counts too.
I've ended up with a solution that mixes the following knowledge based on experience:
You can add a new Fragment at the tail without problems.
You cannot readd a Fragment that was previously removed, as it leads to java.lang.IllegalStateException: Can't change tag of fragment sometimes, so you have to clone it.
For removing a Fragment you have to return PagerAdapter.POSITION_NONE in the method getItemPosition(Object object), and remove the Fragment from the FragmentManager.
If you are adding/removing/replacing in other place different than the tail, you have to remove everything from the position you are changing until the end, do the stuff, and then readd the (cloned) Fragments that you removed.
Here it is a complete FragmentActivity code with a FragmentPagerAdapter that has 3 methods for adding, removing and replacing tabs:
public class TabTestActivity extends FragmentActivity implements
ActionBar.TabListener {
private SectionsPagerAdapter mSectionsPagerAdapter;
private ViewPager mViewPager;
private static int tabCount = 0;
private static String labelString = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
labelString = getString(R.string.title_section);
setContentView(R.layout.activity_tab_test);
final ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
mSectionsPagerAdapter = new SectionsPagerAdapter(
getSupportFragmentManager());
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(final int position) {
(new Handler()).postDelayed(new Runnable() {
#Override
public void run() {
actionBar.setSelectedNavigationItem(position);
}
}, 1);
}
});
for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
actionBar.addTab(actionBar.newTab()
.setText(mSectionsPagerAdapter.getPageTitle(i))
.setTabListener(this));
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.tab_test, menu);
return true;
}
public void addNewTab() {
int position = (mSectionsPagerAdapter.getCount() > 0 ? mViewPager.getCurrentItem() : 0);
mSectionsPagerAdapter.insertFragment(position);
mViewPager.setCurrentItem(position, true);
}
public void removeTab() {
if (mSectionsPagerAdapter.getCount() > 0) {
int position = mViewPager.getCurrentItem();
mSectionsPagerAdapter.removeFragment(position);
}
}
public void replaceTab() {
if (mSectionsPagerAdapter.getCount() > 0) {
int position = mViewPager.getCurrentItem();
mSectionsPagerAdapter.replaceFragment(position);
mViewPager.setCurrentItem(position, false);
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_add_tab:
addNewTab();
return true;
case R.id.action_remove_tab:
removeTab();
return true;
case R.id.action_replace_tab:
replaceTab();
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void onTabSelected(ActionBar.Tab tab,
FragmentTransaction fragmentTransaction) {
mViewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(ActionBar.Tab tab,
FragmentTransaction fragmentTransaction) {
}
#Override
public void onTabReselected(ActionBar.Tab tab,
FragmentTransaction fragmentTransaction) {
}
public class SectionsPagerAdapter extends FragmentPagerAdapter {
private List<Fragment> currentFragments;
private FragmentManager fragmentManager;
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
fragmentManager = fm;
currentFragments = new ArrayList<Fragment>();
}
public void insertFragment(int position) {
// Remove fragments from position
List<Fragment> fragmentsToRemove = new ArrayList<Fragment>(currentFragments.subList(position, currentFragments.size()));
int i = currentFragments.size() - 1;
int j = -1;
int k = i;
while (i >= position) {
currentFragments.remove(i);
i--;
j++;
}
notifyDataSetChanged();
final ActionBar actionBar = getActionBar();
while (k >= position) {
actionBar.removeTabAt(k);
k--;
}
android.support.v4.app.FragmentTransaction transaction = fragmentManager.beginTransaction();
while (j >= 0) {
Fragment fragmentToRemove = fragmentsToRemove.get(j);
transaction.detach(fragmentToRemove);
transaction.remove(fragmentToRemove);
j--;
}
transaction.commit();
fragmentManager.executePendingTransactions();
notifyDataSetChanged();
// Add new fragment
Fragment fragment = new DummySectionFragment();
currentFragments.add(position, fragment);
notifyDataSetChanged();
actionBar.addTab(actionBar.newTab()
.setText(mSectionsPagerAdapter.getPageTitle(position))
.setTabListener(TabTestActivity.this), position);
// Readd fragments
if (fragmentsToRemove.size() > 0) {
i = 1;
for (Fragment fragmentToRemove : fragmentsToRemove) {
currentFragments.add(DummySectionFragment.cloneExistingFragment((DummySectionFragment)fragmentToRemove));
notifyDataSetChanged();
actionBar.addTab(actionBar.newTab()
.setText(mSectionsPagerAdapter.getPageTitle(position + i))
.setTabListener(TabTestActivity.this), position + i);
i++;
}
}
}
public void removeFragment(int position) {
// Remove fragments from position
List<Fragment> fragmentsToRemove = new ArrayList<Fragment>(currentFragments.subList(position, currentFragments.size()));
int i = currentFragments.size() - 1;
int j = -1;
int k = i;
while (i >= position) {
currentFragments.remove(i);
i--;
j++;
}
notifyDataSetChanged();
final ActionBar actionBar = getActionBar();
while (k >= position) {
actionBar.removeTabAt(k);
k--;
}
android.support.v4.app.FragmentTransaction transaction = fragmentManager.beginTransaction();
while (j >= 0) {
Fragment fragmentToRemove = fragmentsToRemove.get(j);
transaction.detach(fragmentToRemove);
transaction.remove(fragmentToRemove);
j--;
}
transaction.commitAllowingStateLoss();
fragmentManager.executePendingTransactions();
notifyDataSetChanged();
// Readd fragments (except one)
if (fragmentsToRemove.size() > 1) {
i = 0;
for (Fragment fragment : fragmentsToRemove.subList(1, fragmentsToRemove.size())) {
currentFragments.add(DummySectionFragment.cloneExistingFragment((DummySectionFragment)fragment));
notifyDataSetChanged();
actionBar.addTab(actionBar.newTab()
.setText(mSectionsPagerAdapter.getPageTitle(position + i))
.setTabListener(TabTestActivity.this), position + i);
i++;
}
}
}
public void replaceFragment(int position) {
// Remove fragments from position
List<Fragment> fragmentsToRemove = new ArrayList<Fragment>(currentFragments.subList(position, currentFragments.size()));
int i = currentFragments.size() - 1;
int j = -1;
int k = i;
while (i >= position) {
currentFragments.remove(i);
i--;
j++;
}
notifyDataSetChanged();
final ActionBar actionBar = getActionBar();
while (k >= position) {
actionBar.removeTabAt(k);
k--;
}
android.support.v4.app.FragmentTransaction transaction = fragmentManager.beginTransaction();
while (j >= 0) {
Fragment fragmentToRemove = fragmentsToRemove.get(j);
transaction.detach(fragmentToRemove);
transaction.remove(fragmentToRemove);
j--;
}
transaction.commit();
fragmentManager.executePendingTransactions();
notifyDataSetChanged();
// Add new fragment
Fragment fragment = new DummySectionFragment();
currentFragments.add(position, fragment);
notifyDataSetChanged();
actionBar.addTab(actionBar.newTab()
.setText(mSectionsPagerAdapter.getPageTitle(position))
.setTabListener(TabTestActivity.this), position);
// Readd fragments (except one)
if (fragmentsToRemove.size() > 0) {
i = 1;
for (Fragment fragmentToRemove : fragmentsToRemove.subList(1, fragmentsToRemove.size())) {
currentFragments.add(DummySectionFragment.cloneExistingFragment((DummySectionFragment)fragmentToRemove));
notifyDataSetChanged();
actionBar.addTab(actionBar.newTab()
.setText(mSectionsPagerAdapter.getPageTitle(position + i))
.setTabListener(TabTestActivity.this), position + i);
i++;
}
}
}
#Override
public Fragment getItem(int position) {
if (currentFragments == null) {
currentFragments = new ArrayList<Fragment>();
}
while (currentFragments.size() <= position) {
currentFragments.add(null);
}
if (currentFragments.get(position) != null) {
return currentFragments.get(position);
}
Fragment fragment = new DummySectionFragment();
currentFragments.set(position, fragment);
return fragment;
}
#Override
public int getCount() {
return currentFragments.size();
}
#Override
public int getItemPosition(Object object) {
int position = currentFragments.indexOf(object);
if (position == -1) {
return PagerAdapter.POSITION_NONE;
}
return position;
}
#Override
public CharSequence getPageTitle(int position) {
return ((DummySectionFragment)getItem(position)).getTitle();
}
}
public static class DummySectionFragment extends Fragment {
private int sectionNumber;
public DummySectionFragment() {
super();
sectionNumber = ++tabCount;
}
public static DummySectionFragment cloneExistingFragment(DummySectionFragment fragment) {
DummySectionFragment cloned = new DummySectionFragment();
// Hack for avoiding autoincrement
--tabCount;
cloned.sectionNumber = fragment.getSectionNumber();
return cloned;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_tab_test_dummy,
container, false);
TextView dummyTextView = (TextView) rootView
.findViewById(R.id.section_label);
dummyTextView.setText(String.format(labelString, sectionNumber));
return rootView;
}
public int getSectionNumber() {
return sectionNumber;
}
public String getTitle() {
return String.format(labelString, sectionNumber);
}
}
}