In tab layout activity my fragment does not refresh while swiping - android

I'm using fragmentpageradapter to inflate fragment , this is my coding.
i'm adding cart item in one fragment and showing it to other fragment and all fragment comes in tab layout.My problem is cart item is not refreshing on other fragment while swiping fragment.
public class ProfileDetailePagerAdapter extends FragmentPagerAdapter
{
int TabCount;
HashMap<Integer, String> mFragmentTags;
FragmentManager mFragmentManager;
private boolean isPagingEnabled = true;
public ProfileDetailePagerAdapter(FragmentManager fm, int TabCount) {
super(fm);
this.TabCount = TabCount;
mFragmentTags = new HashMap<Integer, String>();
mFragmentManager = fm;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
MenuFragment tab1 = new MenuFragment();
return tab1;
case 1:
SelectMember_fragment tab2 = new SelectMember_fragment();
return tab2;
case 2:
ViewCart_fragment tab3 = new ViewCart_fragment();
return tab3;
/* case 3:
PaymentMode_fragment tab4=new PaymentMode_fragment();
return tab4;*/
}
return null;
}
#Override
public int getCount() {
return TabCount;
}
#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);
}
}

Call FragmentStatePagerAdapter
You should use addOnTabSelectedListener
This method is used to add a listener that will be invoked when tab
selection changes.
tabLayoutOBJ.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
public void onTabReselected(TabLayout.Tab tab) {
}
public void onTabSelected(TabLayout.Tab tab){
String getTAB = (String)tab.getText();
// Do your WORK
}
public void onTabUnselected(TabLayout.Tab tab){
}
});

The reason behind it is the implementation of viewpager. There is a property called offset in ViewPager to make ready fragments next to it up to offset(int) value.
Default value is 1. For example, you are in 1st position, 0th and 2nd positions are also created.
On the other hand if you are in 0th position 1st is the only created fragment. If you swipe to right (going 1st) then 2nd is going to be created.
I assume you are changing a shared object from 1st fragment and want to notify 2nd fragment with changed data. To solve it you need to use listener. The elegant way to solve it is adding observer pattern to your shared object.
Good luck
Emre

Related

Communicate between Activity and ViewPager Fragments

Like in the image below, I have an Activity with a ViewPager (with TabLayout) inside.
Now I want to refresh for example the text of an TextView in a ViewPager Fragment. How can I do this?
http://imgur.com/0kWQCfx
[Sorry for the image link, but I don't have enough reputations yet.]
I already tried something like this:
//In Activity
View root = getLayoutInflater().inflate(R.layout.fragment_home, null);
TextView textView = (TextView) root.findViewById(R.id.text_view);
textView.setText("test");
But I get a NullPointerException for the textView.
Edit 1:
I only need to access the components inside one Fragment, so I am not using an array inside the Adapter.
I changed my ViewPagerAdapter to this:
public class ViewPagerAdapter extends FragmentPagerAdapter {
public HomeFragment fragment;
...
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return fragment = new HomeFragment();
case 1:
...
default:
return null;
}
}
But I still get a NullPointerException here:
final Fragment root = viewPagerAdapter.fragment;
Log.d("testtest", String.valueOf(root == null));
Write this code in your activity where you want to change fragment value. Your fragment id is e.g. my_fragment
Fragment frag = getFragmentManager().findFragmentById(R.id.my_fragment)
((TextView) frag.getView().findViewById(R.id.text_view)).setText(s);
You can create your own Adapter:
private class YourAdapter extends FragmentPagerAdapter implements ViewPager.OnPageChangeListener
{
Fragment screens[];
public CheckinHistoryAdapter(FragmentManager fm) {
super(fm);
screens = new Fragment[3];
screens[0] = new CoachFragment();
screens[1] = new LogingFragment();
screens[2] = new HistoryFragment();
}
#Override
public Fragment getItem(int index) {
if(index <= screens.length)
{
return screens[index];
}
return null;
}
#Override
public int getCount() {
return screens.length;
}
}
Use it like this
mViewPager.setAdapter(this.mPagerAdapter);
You can access fragment object with getItem method.
If you only need a reference to one Fragment, then just use the position parameter passed into instantiateItem(), and only assign your Fragment reference for the desired position:
public class ViewPagerAdapter extends FragmentPagerAdapter {
public HomeFragment fragment;
//...
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new HomeFragment(); //modified
case 1:
...
default:
return null;
}
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
if (position == 0) {
fragment = (HomeFragment) createdFragment;
}
return createdFragment;
}
}

Refreshing the layout of a Fragment in ViewPager

I have an activity which uses ViewPager with 4 fragments. I am using FragmentPagerAdapter to populate the pages.
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new FragmentA()
case 1:
return new ProfileFragment();
case 2:
return new FragmentC();
case 3:
return new FragmentD();
}
return null;
}
#Override
public int getCount() {
return 4;
}
}
One of these fragments shows the details of User (ProfileFragment in the above code) with following criteria:
If the user isn't logged in, show login layout
If the user is logged in, show profile layout
Hence, when user opens the app for the first time, the login layout will be shown and when user logs in, the fragment needs to be refreshed to show the profile layout.
The approach I was planning to take was that when user logs in, navigate him to FragmentA and destroy the existing ProfileFragment. The onCreateView method of ProfileFragment handles which layout to show based on login state.
public class ProfileFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
int layout = userLoggedIn() ? R.layout.fragment_user_login : R.layout.fragment_user_profile;
return inflater.inflate(layout, container, false);
}
// Handler for login
public void loginUser() {
((MyActivity) getActivity()).loginUser();
}
}
public class MyActivity extends AppCompatActivity {
private SectionsPagerAdapter mSectionsPagerAdapter;
public void loginUser() {
// 1. Set login details as needed
...
// 2. Navigate to FragmentA
...
// 3. Destroy ProfileFragment
mSectionsPagerAdapter.destroyItem(container, 1, object); // How to get `object` here?
}
}
I have two questions regarding this:
The method destroyItem takes Object object returned from instantiateItem. How to get this value while destroying? Do I need to overwrite instantiateItem and maintain a Map<Integer, Object> from position to object?
Is there a better way to update the view of the fragment without destroying the fragment completely?
I did something like that before using FragmentStatePagerAdapter, you can call notifyDataSetChanged the getItemPosition is called for each existing fragment if you call super.getItemPosition it returns POSITION_UNCHANGED when means the fragment has not changed if you return POSITION_NONE will cause the fragment at that position to be reloaded. you can use that in the getItem method to provide the profile fragment.
in this solution you just need to call setLoggedIn
public class SectionsPagerAdapter extends FragmentStatePagerAdapter {
boolean isLoggedIn = false;
boolean userHasJustLoggedIn = false;
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
public void setIsLoggedIn() {
isLoggedIn = true;
userHasJustLoggedIn = true;
notifyDataSetChanged();
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new FragmentA()
case 1:
if (isLoggedIn) {
return new ProfileFragment();
} else {
return new LoginFragment();
}
case 2:
return new FragmentC();
case 3:
return new FragmentD();
}
return null;
}
#Override
public int getItemPosition(Object object) {
Fragment fragment = (Fragment) object;
if (fragment instanceof LoginFragment && userHasJustLoggedIn) {
userHasJustLoggedIn = false;
return POSITION_NONE;
}
return super.getItemPosition(object);
}
#Override
public int getCount() {
return 4;
}
}

Android FragmentPagerAdapter with same fragment class

I'm making a score keeping app using tabs, tablayout, fragments, and a FragmentPagerAdapter. The app runs fine, but I can't figure out how I can access the fragments. After doing some work in the fragment, I want to access the fragment to update its score. How can I access the current fragment?
Edit - my main problem is finding the position. I can use tabLayout.getSelectedTabPosition() to get the correct position if I clicked on the tab, but if I swipe between tabs, viewpager handles it differently and I don't know how to find the position from that.
public class PagerAdapter extends FragmentPagerAdapter {
private int numTabs;
public PagerAdapter(FragmentManager fm, int tabs) {
super(fm);
numTabs = tabs;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return makeScoreFrag(1, "Team 1");
case 1:
return makeScoreFrag(2, "Team 2");
case 2:
return makeScoreFrag(3, "Team 3");
case 3:
return makeScoreFrag(4, "Team 4");
default:
return null;
}
}
#Override
public int getCount() {
return numTabs;
}
private ScoreFragment makeScoreFrag(int num, String title){
Bundle bundle = new Bundle();
bundle.putInt("score", 0);
bundle.putInt("number", num);
bundle.putString("title", title);
ScoreFragment frag = new ScoreFragment();
frag.setArguments(bundle);
return frag;
}
}
You can Override instantiateItem method in FragmentPagerAdapter
ScoreFragment[] fragments = new ScoreFragment[numTabs];
#Override
public Object instantiateItem(ViewGroup container, final int position) {
fragments[position] = (ScoreFragment) super.instantiateItem(container, position);
return fragments[position];
}
Then current fragment is:
ScoreFragment curFragment = fragments[curTab];

Navigating between Fragments while using a ViewPager

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);
}
}

Replace Fragment inside a ViewPager

I'm trying to use Fragment with a ViewPager using the FragmentPagerAdapter.
What I'm looking for to achieve is to replace a fragment, positioned on the first page of the ViewPager, with another one.
The pager is composed of two pages. The first one is the FirstPagerFragment, the second one is the SecondPagerFragment. Clicking on a button of the first page. I'd like to replace the FirstPagerFragment with the NextFragment.
There is my code below.
public class FragmentPagerActivity extends FragmentActivity {
static final int NUM_ITEMS = 2;
MyAdapter mAdapter;
ViewPager mPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_pager);
mAdapter = new MyAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
}
/**
* Pager Adapter
*/
public static class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
if(position == 0) {
return FirstPageFragment.newInstance();
} else {
return SecondPageFragment.newInstance();
}
}
}
/**
* Second Page FRAGMENT
*/
public static class SecondPageFragment extends Fragment {
public static SecondPageFragment newInstance() {
SecondPageFragment f = new SecondPageFragment();
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Log.d("DEBUG", "onCreateView");
return inflater.inflate(R.layout.second, container, false);
}
}
/**
* FIRST PAGE FRAGMENT
*/
public static class FirstPageFragment extends Fragment {
Button button;
public static FirstPageFragment newInstance() {
FirstPageFragment f = new FirstPageFragment();
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Log.d("DEBUG", "onCreateView");
View root = inflater.inflate(R.layout.first, container, false);
button = (Button) root.findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
FragmentTransaction trans = getFragmentManager().beginTransaction();
trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());
trans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
trans.addToBackStack(null);
trans.commit();
}
});
return root;
}
/**
* Next Page FRAGMENT in the First Page
*/
public static class NextFragment extends Fragment {
public static NextFragment newInstance() {
NextFragment f = new NextFragment();
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Log.d("DEBUG", "onCreateView");
return inflater.inflate(R.layout.next, container, false);
}
}
}
...and here the xml files
fragment_pager.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:padding="4dip"
android:gravity="center_horizontal"
android:layout_width="match_parent" android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
</android.support.v4.view.ViewPager>
</LinearLayout>
first.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/first_fragment_root_id"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:id="#+id/button"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="to next"/>
</LinearLayout>
Now the problem... which ID should I use in
trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());
?
If I use R.id.first_fragment_root_id, the replacement works, but Hierarchy Viewer shows a strange behavior, as below.
At the beginning the situation is
after the replacement the situation is
As you can see there is something wrong, I expect to find the same state shown as in the first picture after I replace the fragment.
There is another solution that does not need modifying source code of ViewPager and FragmentStatePagerAdapter, and it works with the FragmentPagerAdapter base class used by the author.
I'd like to start by answering the author's question about which ID he should use; it is ID of the container, i.e. ID of the view pager itself. However, as you probably noticed yourself, using that ID in your code causes nothing to happen. I will explain why:
First of all, to make ViewPager repopulate the pages, you need to call notifyDataSetChanged() that resides in the base class of your adapter.
Second, ViewPager uses the getItemPosition() abstract method to check which pages should be destroyed and which should be kept. The default implementation of this function always returns POSITION_UNCHANGED, which causes ViewPager to keep all current pages, and consequently not attaching your new page. Thus, to make fragment replacement work, getItemPosition() needs to be overridden in your adapter and must return POSITION_NONE when called with an old, to be hidden, fragment as argument.
This also means that your adapter always needs to be aware of which fragment that should be displayed in position 0, FirstPageFragment or NextFragment. One way of doing this is supplying a listener when creating FirstPageFragment, which will be called when it is time to switch fragments. I think this is a good thing though, to let your fragment adapter handle all fragment switches and calls to ViewPager and FragmentManager.
Third, FragmentPagerAdapter caches the used fragments by a name which is derived from the position, so if there was a fragment at position 0, it will not be replaced even though the class is new. There are two solutions, but the simplest is to use the remove() function of FragmentTransaction, which will remove its tag as well.
That was a lot of text, here is code that should work in your case:
public class MyAdapter extends FragmentPagerAdapter
{
static final int NUM_ITEMS = 2;
private final FragmentManager mFragmentManager;
private Fragment mFragmentAtPos0;
public MyAdapter(FragmentManager fm)
{
super(fm);
mFragmentManager = fm;
}
#Override
public Fragment getItem(int position)
{
if (position == 0)
{
if (mFragmentAtPos0 == null)
{
mFragmentAtPos0 = FirstPageFragment.newInstance(new FirstPageFragmentListener()
{
public void onSwitchToNextFragment()
{
mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();
mFragmentAtPos0 = NextFragment.newInstance();
notifyDataSetChanged();
}
});
}
return mFragmentAtPos0;
}
else
return SecondPageFragment.newInstance();
}
#Override
public int getCount()
{
return NUM_ITEMS;
}
#Override
public int getItemPosition(Object object)
{
if (object instanceof FirstPageFragment && mFragmentAtPos0 instanceof NextFragment)
return POSITION_NONE;
return POSITION_UNCHANGED;
}
}
public interface FirstPageFragmentListener
{
void onSwitchToNextFragment();
}
As of November 13th 2012, repacing fragments in a ViewPager seems to have become a lot easier. Google released Android 4.2 with support for nested fragments, and it's also supported in the new Android Support Library v11 so this will work all the way back to 1.6
It's very similiar to the normal way of replacing a fragment except you use getChildFragmentManager. It seems to work except the nested fragment backstack isn't popped when the user clicks the back button. As per the solution in that linked question, you need to manually call the popBackStackImmediate() on the child manager of the fragment. So you need to override onBackPressed() of the ViewPager activity where you'll get the current fragment of the ViewPager and call getChildFragmentManager().popBackStackImmediate() on it.
Getting the Fragment currently being displayed is a bit hacky as well, I used this dirty "android:switcher:VIEWPAGER_ID:INDEX" solution but you can also keep track of all fragments of the ViewPager yourself as explained in the second solution on this page.
So here's my code for a ViewPager with 4 ListViews with a detail view shown in the ViewPager when the user clicks a row, and with the back button working. I tried to include just the relevant code for the sake of brevity so leave a comment if you want the full app uploaded to GitHub.
HomeActivity.java
public class HomeActivity extends SherlockFragmentActivity {
FragmentAdapter mAdapter;
ViewPager mPager;
TabPageIndicator mIndicator;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mAdapter = new FragmentAdapter(getSupportFragmentManager());
mPager = (ViewPager)findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mIndicator = (TabPageIndicator)findViewById(R.id.indicator);
mIndicator.setViewPager(mPager);
}
// This the important bit to make sure the back button works when you're nesting fragments. Very hacky, all it takes is some Google engineer to change that ViewPager view tag to break this in a future Android update.
#Override
public void onBackPressed() {
Fragment fragment = (Fragment) getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.pager + ":"+mPager.getCurrentItem());
if (fragment != null) // could be null if not instantiated yet
{
if (fragment.getView() != null) {
// Pop the backstack on the ChildManager if there is any. If not, close this activity as normal.
if (!fragment.getChildFragmentManager().popBackStackImmediate()) {
finish();
}
}
}
}
class FragmentAdapter extends FragmentPagerAdapter {
public FragmentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return ListProductsFragment.newInstance();
case 1:
return ListActiveSubstancesFragment.newInstance();
case 2:
return ListProductFunctionsFragment.newInstance();
case 3:
return ListCropsFragment.newInstance();
default:
return null;
}
}
#Override
public int getCount() {
return 4;
}
}
}
ListProductsFragment.java
public class ListProductsFragment extends SherlockFragment {
private ListView list;
public static ListProductsFragment newInstance() {
ListProductsFragment f = new ListProductsFragment();
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View V = inflater.inflate(R.layout.list, container, false);
list = (ListView)V.findViewById(android.R.id.list);
list.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
// This is important bit
Fragment productDetailFragment = FragmentProductDetail.newInstance();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.addToBackStack(null);
transaction.replace(R.id.products_list_linear, productDetailFragment).commit();
}
});
return V;
}
}
Based on #wize 's answer, which I found helpful and elegant, I could achieve what I wanted partially, cause I wanted the cability to go back to the first Fragment once replaced. I achieved it bit modifying a bit his code.
This would be the FragmentPagerAdapter:
public static class MyAdapter extends FragmentPagerAdapter {
private final class CalendarPageListener implements
CalendarPageFragmentListener {
public void onSwitchToNextFragment() {
mFragmentManager.beginTransaction().remove(mFragmentAtPos0)
.commit();
if (mFragmentAtPos0 instanceof FirstFragment){
mFragmentAtPos0 = NextFragment.newInstance(listener);
}else{ // Instance of NextFragment
mFragmentAtPos0 = FirstFragment.newInstance(listener);
}
notifyDataSetChanged();
}
}
CalendarPageListener listener = new CalendarPageListener();;
private Fragment mFragmentAtPos0;
private FragmentManager mFragmentManager;
public MyAdapter(FragmentManager fm) {
super(fm);
mFragmentManager = fm;
}
#Override
public int getCount() {
return NUM_ITEMS;
}
#Override
public int getItemPosition(Object object) {
if (object instanceof FirstFragment && mFragmentAtPos0 instanceof NextFragment)
return POSITION_NONE;
if (object instanceof NextFragment && mFragmentAtPos0 instanceof FirstFragment)
return POSITION_NONE;
return POSITION_UNCHANGED;
}
#Override
public Fragment getItem(int position) {
if (position == 0)
return Portada.newInstance();
if (position == 1) { // Position where you want to replace fragments
if (mFragmentAtPos0 == null) {
mFragmentAtPos0 = FirstFragment.newInstance(listener);
}
return mFragmentAtPos0;
}
if (position == 2)
return Clasificacion.newInstance();
if (position == 3)
return Informacion.newInstance();
return null;
}
}
public interface CalendarPageFragmentListener {
void onSwitchToNextFragment();
}
To perfom the replacement, simply define a static field, of the type CalendarPageFragmentListener and initialized through the newInstance methods of the corresponding fragments and call FirstFragment.pageListener.onSwitchToNextFragment() or NextFragment.pageListener.onSwitchToNextFragment() respictevely.
I have implemented a solution for:
Dynamic fragment replacement inside the tab
Maintenance of the history per tab
Working with orientation changes
The tricks to achieve this are the following:
Use the notifyDataSetChanged() method to apply the fragment replacement
Use the fragment manager only for back stage and no for fragament replacement
Maintain the history using the memento pattern (stack)
The adapter code is the following:
public class TabsAdapter extends FragmentStatePagerAdapter implements ActionBar.TabListener, ViewPager.OnPageChangeListener {
/** The sherlock fragment activity. */
private final SherlockFragmentActivity mActivity;
/** The action bar. */
private final ActionBar mActionBar;
/** The pager. */
private final ViewPager mPager;
/** The tabs. */
private List<TabInfo> mTabs = new LinkedList<TabInfo>();
/** The total number of tabs. */
private int TOTAL_TABS;
private Map<Integer, Stack<TabInfo>> history = new HashMap<Integer, Stack<TabInfo>>();
/**
* Creates a new instance.
*
* #param activity the activity
* #param pager the pager
*/
public TabsAdapter(SherlockFragmentActivity activity, ViewPager pager) {
super(activity.getSupportFragmentManager());
activity.getSupportFragmentManager();
this.mActivity = activity;
this.mActionBar = activity.getSupportActionBar();
this.mPager = pager;
mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
}
/**
* Adds the tab.
*
* #param image the image
* #param fragmentClass the class
* #param args the arguments
*/
public void addTab(final Drawable image, final Class fragmentClass, final Bundle args) {
final TabInfo tabInfo = new TabInfo(fragmentClass, args);
final ActionBar.Tab tab = mActionBar.newTab();
tab.setTabListener(this);
tab.setTag(tabInfo);
tab.setIcon(image);
mTabs.add(tabInfo);
mActionBar.addTab(tab);
notifyDataSetChanged();
}
#Override
public Fragment getItem(final int position) {
final TabInfo tabInfo = mTabs.get(position);
return Fragment.instantiate(mActivity, tabInfo.fragmentClass.getName(), tabInfo.args);
}
#Override
public int getItemPosition(final Object object) {
/* Get the current position. */
int position = mActionBar.getSelectedTab().getPosition();
/* The default value. */
int pos = POSITION_NONE;
if (history.get(position).isEmpty()) {
return POSITION_NONE;
}
/* Checks if the object exists in current history. */
for (Stack<TabInfo> stack : history.values()) {
TabInfo c = stack.peek();
if (c.fragmentClass.getName().equals(object.getClass().getName())) {
pos = POSITION_UNCHANGED;
break;
}
}
return pos;
}
#Override
public int getCount() {
return mTabs.size();
}
#Override
public void onPageScrollStateChanged(int arg0) {
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
#Override
public void onPageSelected(int position) {
mActionBar.setSelectedNavigationItem(position);
}
#Override
public void onTabSelected(final ActionBar.Tab tab, final FragmentTransaction ft) {
TabInfo tabInfo = (TabInfo) tab.getTag();
for (int i = 0; i < mTabs.size(); i++) {
if (mTabs.get(i).equals(tabInfo)) {
mPager.setCurrentItem(i);
}
}
}
#Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
#Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
public void replace(final int position, final Class fragmentClass, final Bundle args) {
/* Save the fragment to the history. */
mActivity.getSupportFragmentManager().beginTransaction().addToBackStack(null).commit();
/* Update the tabs. */
updateTabs(new TabInfo(fragmentClass, args), position);
/* Updates the history. */
history.get(position).push(new TabInfo(mTabs.get(position).fragmentClass, mTabs.get(position).args));
notifyDataSetChanged();
}
/**
* Updates the tabs.
*
* #param tabInfo
* the new tab info
* #param position
* the position
*/
private void updateTabs(final TabInfo tabInfo, final int position) {
mTabs.remove(position);
mTabs.add(position, tabInfo);
mActionBar.getTabAt(position).setTag(tabInfo);
}
/**
* Creates the history using the current state.
*/
public void createHistory() {
int position = 0;
TOTAL_TABS = mTabs.size();
for (TabInfo mTab : mTabs) {
if (history.get(position) == null) {
history.put(position, new Stack<TabInfo>());
}
history.get(position).push(new TabInfo(mTab.fragmentClass, mTab.args));
position++;
}
}
/**
* Called on back
*/
public void back() {
int position = mActionBar.getSelectedTab().getPosition();
if (!historyIsEmpty(position)) {
/* In case there is not any other item in the history, then finalize the activity. */
if (isLastItemInHistory(position)) {
mActivity.finish();
}
final TabInfo currentTabInfo = getPrevious(position);
mTabs.clear();
for (int i = 0; i < TOTAL_TABS; i++) {
if (i == position) {
mTabs.add(new TabInfo(currentTabInfo.fragmentClass, currentTabInfo.args));
} else {
TabInfo otherTabInfo = history.get(i).peek();
mTabs.add(new TabInfo(otherTabInfo.fragmentClass, otherTabInfo.args));
}
}
}
mActionBar.selectTab(mActionBar.getTabAt(position));
notifyDataSetChanged();
}
/**
* Returns if the history is empty.
*
* #param position
* the position
* #return the flag if empty
*/
private boolean historyIsEmpty(final int position) {
return history == null || history.isEmpty() || history.get(position).isEmpty();
}
private boolean isLastItemInHistory(final int position) {
return history.get(position).size() == 1;
}
/**
* Returns the previous state by the position provided.
*
* #param position
* the position
* #return the tab info
*/
private TabInfo getPrevious(final int position) {
TabInfo currentTabInfo = history.get(position).pop();
if (!history.get(position).isEmpty()) {
currentTabInfo = history.get(position).peek();
}
return currentTabInfo;
}
/** The tab info class */
private static class TabInfo {
/** The fragment class. */
public Class fragmentClass;
/** The args.*/
public Bundle args;
/**
* Creates a new instance.
*
* #param fragmentClass
* the fragment class
* #param args
* the args
*/
public TabInfo(Class fragmentClass, Bundle args) {
this.fragmentClass = fragmentClass;
this.args = args;
}
#Override
public boolean equals(final Object o) {
return this.fragmentClass.getName().equals(o.getClass().getName());
}
#Override
public int hashCode() {
return fragmentClass.getName() != null ? fragmentClass.getName().hashCode() : 0;
}
#Override
public String toString() {
return "TabInfo{" +
"fragmentClass=" + fragmentClass +
'}';
}
}
The very first time you add all tabs, we need to call the method createHistory(), to create the initial history
public void createHistory() {
int position = 0;
TOTAL_TABS = mTabs.size();
for (TabInfo mTab : mTabs) {
if (history.get(position) == null) {
history.put(position, new Stack<TabInfo>());
}
history.get(position).push(new TabInfo(mTab.fragmentClass, mTab.args));
position++;
}
}
Every time you want to replace a fragment to a specific tab you call:
replace(final int position, final Class fragmentClass, final Bundle args)
/* Save the fragment to the history. */
mActivity.getSupportFragmentManager().beginTransaction().addToBackStack(null).commit();
/* Update the tabs. */
updateTabs(new TabInfo(fragmentClass, args), position);
/* Updates the history. */
history.get(position).push(new TabInfo(mTabs.get(position).fragmentClass, mTabs.get(position).args));
notifyDataSetChanged();
On back pressed you need to call the back() method:
public void back() {
int position = mActionBar.getSelectedTab().getPosition();
if (!historyIsEmpty(position)) {
/* In case there is not any other item in the history, then finalize the activity. */
if (isLastItemInHistory(position)) {
mActivity.finish();
}
final TabInfo currentTabInfo = getPrevious(position);
mTabs.clear();
for (int i = 0; i < TOTAL_TABS; i++) {
if (i == position) {
mTabs.add(new TabInfo(currentTabInfo.fragmentClass, currentTabInfo.args));
} else {
TabInfo otherTabInfo = history.get(i).peek();
mTabs.add(new TabInfo(otherTabInfo.fragmentClass, otherTabInfo.args));
}
}
}
mActionBar.selectTab(mActionBar.getTabAt(position));
notifyDataSetChanged();
}
The solution works with sherlock action bar and with swipe gesture.
tl;dr: Use a host fragment that is responsible for replacing its hosted content and keeps track of a back navigation history (like in a browser).
As your use case consists of a fixed amount of tabs my solution works well: The idea is to fill the ViewPager with instances of a custom class HostFragment, that is able to replace its hosted content and keeps its own back navigation history. To replace the hosted fragment you make a call to the method hostfragment.replaceFragment():
public void replaceFragment(Fragment fragment, boolean addToBackstack) {
if (addToBackstack) {
getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).addToBackStack(null).commit();
} else {
getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).commit();
}
}
All that method does is to replace the frame layout with the id R.id.hosted_fragment with the fragment provided to the method.
Check my tutorial on this topic for further details and a complete working example on GitHub!
Some of the presented solutions helped me a lot to partially solve the problem but there is still one important thing missing in the solutions which has produced unexpected exceptions and black page content instead of fragment content in some cases.
The thing is that FragmentPagerAdapter class is using item ID to store cached fragments to FragmentManager. For this reason, you need to override also the getItemId(int position) method so that it returns e. g. position for top-level pages and 100 + position for details pages. Otherwise the previously created top-level fragment would be returned from the cache instead of detail-level fragment.
Furthermore, I'm sharing here a complete example how to implement tabs-like activity with Fragment pages using ViewPager and tab buttons using RadioGroup that allows replacement of top-level pages with detailed pages and also supports back button. This implementation supports only one level of back stacking (item list - item details) but multi-level back stacking implementation is straightforward. This example works pretty well in normal cases except of it is throwing a NullPointerException in case when you switch to e. g. second page, change the fragment of the first page (while not visible) and return back to the first page. I'll post a solution to this issue once I'll figure it out:
public class TabsActivity extends FragmentActivity {
public static final int PAGE_COUNT = 3;
public static final int FIRST_PAGE = 0;
public static final int SECOND_PAGE = 1;
public static final int THIRD_PAGE = 2;
/**
* Opens a new inferior page at specified tab position and adds the current page into back
* stack.
*/
public void startPage(int position, Fragment content) {
// Replace page adapter fragment at position.
mPagerAdapter.start(position, content);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Initialize basic layout.
this.setContentView(R.layout.tabs_activity);
// Add tab fragments to view pager.
{
// Create fragments adapter.
mPagerAdapter = new PagerAdapter(pager);
ViewPager pager = (ViewPager) super.findViewById(R.id.tabs_view_pager);
pager.setAdapter(mPagerAdapter);
// Update active tab in tab bar when page changes.
pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int index, float value, int nextIndex) {
// Not used.
}
#Override
public void onPageSelected(int index) {
RadioGroup tabs_radio_group = (RadioGroup) TabsActivity.this.findViewById(
R.id.tabs_radio_group);
switch (index) {
case 0: {
tabs_radio_group.check(R.id.first_radio_button);
}
break;
case 1: {
tabs_radio_group.check(R.id.second_radio_button);
}
break;
case 2: {
tabs_radio_group.check(R.id.third_radio_button);
}
break;
}
}
#Override
public void onPageScrollStateChanged(int index) {
// Not used.
}
});
}
// Set "tabs" radio group on checked change listener that changes the displayed page.
RadioGroup radio_group = (RadioGroup) this.findViewById(R.id.tabs_radio_group);
radio_group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(RadioGroup radioGroup, int id) {
// Get view pager representing tabs.
ViewPager view_pager = (ViewPager) TabsActivity.this.findViewById(R.id.tabs_view_pager);
if (view_pager == null) {
return;
}
// Change the active page.
switch (id) {
case R.id.first_radio_button: {
view_pager.setCurrentItem(FIRST_PAGE);
}
break;
case R.id.second_radio_button: {
view_pager.setCurrentItem(SECOND_PAGE);
}
break;
case R.id.third_radio_button: {
view_pager.setCurrentItem(THIRD_PAGE);
}
break;
}
});
}
}
#Override
public void onBackPressed() {
if (!mPagerAdapter.back()) {
super.onBackPressed();
}
}
/**
* Serves the fragments when paging.
*/
private class PagerAdapter extends FragmentPagerAdapter {
public PagerAdapter(ViewPager container) {
super(TabsActivity.this.getSupportFragmentManager());
mContainer = container;
mFragmentManager = TabsActivity.this.getSupportFragmentManager();
// Prepare "empty" list of fragments.
mFragments = new ArrayList<Fragment>(){};
mBackFragments = new ArrayList<Fragment>(){};
for (int i = 0; i < PAGE_COUNT; i++) {
mFragments.add(null);
mBackFragments.add(null);
}
}
/**
* Replaces the view pager fragment at specified position.
*/
public void replace(int position, Fragment fragment) {
// Get currently active fragment.
Fragment old_fragment = mFragments.get(position);
if (old_fragment == null) {
return;
}
// Replace the fragment using transaction and in underlaying array list.
// NOTE .addToBackStack(null) doesn't work
this.startUpdate(mContainer);
mFragmentManager.beginTransaction().setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.remove(old_fragment).add(mContainer.getId(), fragment)
.commit();
mFragments.set(position, fragment);
this.notifyDataSetChanged();
this.finishUpdate(mContainer);
}
/**
* Replaces the fragment at specified position and stores the current fragment to back stack
* so it can be restored by #back().
*/
public void start(int position, Fragment fragment) {
// Remember current fragment.
mBackFragments.set(position, mFragments.get(position));
// Replace the displayed fragment.
this.replace(position, fragment);
}
/**
* Replaces the current fragment by fragment stored in back stack. Does nothing and returns
* false if no fragment is back-stacked.
*/
public boolean back() {
int position = mContainer.getCurrentItem();
Fragment fragment = mBackFragments.get(position);
if (fragment == null) {
// Nothing to go back.
return false;
}
// Restore the remembered fragment and remove it from back fragments.
this.replace(position, fragment);
mBackFragments.set(position, null);
return true;
}
/**
* Returns fragment of a page at specified position.
*/
#Override
public Fragment getItem(int position) {
// If fragment not yet initialized, create its instance.
if (mFragments.get(position) == null) {
switch (position) {
case FIRST_PAGE: {
mFragments.set(FIRST_PAGE, new DefaultFirstFragment());
}
break;
case SECOND_PAGE: {
mFragments.set(SECOND_PAGE, new DefaultSecondFragment());
}
break;
case THIRD_PAGE: {
mFragments.set(THIRD_PAGE, new DefaultThirdFragment());
}
break;
}
}
// Return fragment instance at requested position.
return mFragments.get(position);
}
/**
* Custom item ID resolution. Needed for proper page fragment caching.
* #see FragmentPagerAdapter#getItemId(int).
*/
#Override
public long getItemId(int position) {
// Fragments from second level page hierarchy have their ID raised above 100. This is
// important to FragmentPagerAdapter because it is caching fragments to FragmentManager with
// this item ID key.
Fragment item = mFragments.get(position);
if (item != null) {
if ((item instanceof NewFirstFragment) || (item instanceof NewSecondFragment) ||
(item instanceof NewThirdFragment)) {
return 100 + position;
}
}
return position;
}
/**
* Returns number of pages.
*/
#Override
public int getCount() {
return mFragments.size();
}
#Override
public int getItemPosition(Object object)
{
int position = POSITION_UNCHANGED;
if ((object instanceof DefaultFirstFragment) || (object instanceof NewFirstFragment)) {
if (object.getClass() != mFragments.get(FIRST_PAGE).getClass()) {
position = POSITION_NONE;
}
}
if ((object instanceof DefaultSecondragment) || (object instanceof NewSecondFragment)) {
if (object.getClass() != mFragments.get(SECOND_PAGE).getClass()) {
position = POSITION_NONE;
}
}
if ((object instanceof DefaultThirdFragment) || (object instanceof NewThirdFragment)) {
if (object.getClass() != mFragments.get(THIRD_PAGE).getClass()) {
position = POSITION_NONE;
}
}
return position;
}
private ViewPager mContainer;
private FragmentManager mFragmentManager;
/**
* List of page fragments.
*/
private List<Fragment> mFragments;
/**
* List of page fragments to return to in onBack();
*/
private List<Fragment> mBackFragments;
}
/**
* Tab fragments adapter.
*/
private PagerAdapter mPagerAdapter;
}
I have created a ViewPager with 3 elements and 2 sub elements for index 2 and 3 and here what I wanted to do..
I have implemented this with the help from previous questions and answers from StackOverFlow and here is the link.
ViewPagerChildFragments
To replace a fragment inside a ViewPager you can move source codes of ViewPager, PagerAdapter and FragmentStatePagerAdapter classes into your project and add following code.
into ViewPager:
public void notifyItemChanged(Object oldItem, Object newItem) {
if (mItems != null) {
for (ItemInfo itemInfo : mItems) {
if (itemInfo.object.equals(oldItem)) {
itemInfo.object = newItem;
}
}
}
invalidate();
}
into FragmentStatePagerAdapter:
public void replaceFragmetns(ViewPager container, Fragment oldFragment, Fragment newFragment) {
startUpdate(container);
// remove old fragment
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
int position = getFragmentPosition(oldFragment);
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, null);
mFragments.set(position, null);
mCurTransaction.remove(oldFragment);
// add new fragment
while (mFragments.size() <= position) {
mFragments.add(null);
}
mFragments.set(position, newFragment);
mCurTransaction.add(container.getId(), newFragment);
finishUpdate(container);
// ensure getItem returns newFragemtn after calling handleGetItemInbalidated()
handleGetItemInbalidated(container, oldFragment, newFragment);
container.notifyItemChanged(oldFragment, newFragment);
}
protected abstract void handleGetItemInbalidated(View container, Fragment oldFragment, Fragment newFragment);
protected abstract int getFragmentPosition(Fragment fragment);
handleGetItemInvalidated() ensures that after next call of getItem() it return newFragment
getFragmentPosition() returns position of the fragment in your adapter.
Now, to replace fragments call
mAdapter.replaceFragmetns(mViewPager, oldFragment, newFragment);
If you interested in an example project ask me for the sources.
Works Great with AndroidTeam's solution, however I found that I needed the ability to go back much like FrgmentTransaction.addToBackStack(null) But merely adding this will only cause the Fragment to be replaced without notifying the ViewPager. Combining the provided solution with this minor enhancement will allow you to return to the previous state by merely overriding the activity's onBackPressed() method. The biggest drawback is that it will only go back one at a time which may result in multiple back clicks
private ArrayList<Fragment> bFragments = new ArrayList<Fragment>();
private ArrayList<Integer> bPosition = new ArrayList<Integer>();
public void replaceFragmentsWithBackOut(ViewPager container, Fragment oldFragment, Fragment newFragment) {
startUpdate(container);
// remove old fragment
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
int position = getFragmentPosition(oldFragment);
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
//Add Fragment to Back List
bFragments.add(oldFragment);
//Add Pager Position to Back List
bPosition.add(position);
mSavedState.set(position, null);
mFragments.set(position, null);
mCurTransaction.remove(oldFragment);
// add new fragment
while (mFragments.size() <= position) {
mFragments.add(null);
}
mFragments.set(position, newFragment);
mCurTransaction.add(container.getId(), newFragment);
finishUpdate(container);
// ensure getItem returns newFragemtn after calling handleGetItemInbalidated()
handleGetItemInvalidated(container, oldFragment, newFragment);
container.notifyItemChanged(oldFragment, newFragment);
}
public boolean popBackImmediate(ViewPager container){
int bFragSize = bFragments.size();
int bPosSize = bPosition.size();
if(bFragSize>0 && bPosSize>0){
if(bFragSize==bPosSize){
int last = bFragSize-1;
int position = bPosition.get(last);
//Returns Fragment Currently at this position
Fragment replacedFragment = mFragments.get(position);
Fragment originalFragment = bFragments.get(last);
this.replaceFragments(container, replacedFragment, originalFragment);
bPosition.remove(last);
bFragments.remove(last);
return true;
}
}
return false;
}
Hope this helps someone.
Also as far as getFragmentPosition() goes it's pretty much getItem() in reverse. You know which fragments go where, just make sure you return the correct position it will be in. Here's an example:
#Override
protected int getFragmentPosition(Fragment fragment) {
if(fragment.equals(originalFragment1)){
return 0;
}
if(fragment.equals(replacementFragment1)){
return 0;
}
if(fragment.equals(Fragment2)){
return 1;
}
return -1;
}
In your onCreateView method, container is actually a ViewPager instance.
So, just calling
ViewPager vpViewPager = (ViewPager) container;
vpViewPager.setCurrentItem(1);
will change current fragment in your ViewPager.
Here's my relatively simple solution to this problem. The keys to this solution are to use FragmentStatePagerAdapter instead of FragmentPagerAdapter as the former will remove unused fragments for you while the later still retains their instances. The second is the use of POSITION_NONE in getItem(). I've used a simple List to keep track of my fragments. My requirement was to replace the entire list of fragments at once with a new list, but the below could be easily modified to replace individual fragments:
public class MyFragmentAdapter extends FragmentStatePagerAdapter {
private List<Fragment> fragmentList = new ArrayList<Fragment>();
private List<String> tabTitleList = new ArrayList<String>();
public MyFragmentAdapter(FragmentManager fm) {
super(fm);
}
public void addFragments(List<Fragment> fragments, List<String> titles) {
fragmentList.clear();
tabTitleList.clear();
fragmentList.addAll(fragments);
tabTitleList.addAll(titles);
notifyDataSetChanged();
}
#Override
public int getItemPosition(Object object) {
if (fragmentList.contains(object)) {
return POSITION_UNCHANGED;
}
return POSITION_NONE;
}
#Override
public Fragment getItem(int item) {
if (item >= fragmentList.size()) {
return null;
}
return fragmentList.get(item);
}
#Override
public int getCount() {
return fragmentList.size();
}
#Override
public CharSequence getPageTitle(int position) {
return tabTitleList.get(position);
}
}
I also made a solution, which is working with Stacks. It's a more modular approach so u don't have to specify each Fragment and Detail Fragment in your FragmentPagerAdapter. It's build on top of the Example from ActionbarSherlock which derives if I'm right from the Google Demo App.
/**
* This is a helper class that implements the management of tabs and all
* details of connecting a ViewPager with associated TabHost. It relies on a
* trick. Normally a tab host has a simple API for supplying a View or
* Intent that each tab will show. This is not sufficient for switching
* between pages. So instead we make the content part of the tab host
* 0dp high (it is not shown) and the TabsAdapter supplies its own dummy
* view to show as the tab content. It listens to changes in tabs, and takes
* care of switch to the correct paged in the ViewPager whenever the selected
* tab changes.
*
* Changed to support more Layers of fragments on each Tab.
* by sebnapi (2012)
*
*/
public class TabsAdapter extends FragmentPagerAdapter
implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener {
private final Context mContext;
private final TabHost mTabHost;
private final ViewPager mViewPager;
private ArrayList<String> mTabTags = new ArrayList<String>();
private HashMap<String, Stack<TabInfo>> mTabStackMap = new HashMap<String, Stack<TabInfo>>();
static final class TabInfo {
public final String tag;
public final Class<?> clss;
public Bundle args;
TabInfo(String _tag, Class<?> _class, Bundle _args) {
tag = _tag;
clss = _class;
args = _args;
}
}
static class DummyTabFactory implements TabHost.TabContentFactory {
private final Context mContext;
public DummyTabFactory(Context context) {
mContext = context;
}
#Override
public View createTabContent(String tag) {
View v = new View(mContext);
v.setMinimumWidth(0);
v.setMinimumHeight(0);
return v;
}
}
public interface SaveStateBundle{
public Bundle onRemoveFragment(Bundle outState);
}
public TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager pager) {
super(activity.getSupportFragmentManager());
mContext = activity;
mTabHost = tabHost;
mViewPager = pager;
mTabHost.setOnTabChangedListener(this);
mViewPager.setAdapter(this);
mViewPager.setOnPageChangeListener(this);
}
/**
* Add a Tab which will have Fragment Stack. Add Fragments on this Stack by using
* addFragment(FragmentManager fm, String _tag, Class<?> _class, Bundle _args)
* The Stack will hold always the default Fragment u add here.
*
* DON'T ADD Tabs with same tag, it's not beeing checked and results in unexpected
* beahvior.
*
* #param tabSpec
* #param clss
* #param args
*/
public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args){
Stack<TabInfo> tabStack = new Stack<TabInfo>();
tabSpec.setContent(new DummyTabFactory(mContext));
mTabHost.addTab(tabSpec);
String tag = tabSpec.getTag();
TabInfo info = new TabInfo(tag, clss, args);
mTabTags.add(tag); // to know the position of the tab tag
tabStack.add(info);
mTabStackMap.put(tag, tabStack);
notifyDataSetChanged();
}
/**
* Will add the Fragment to Tab with the Tag _tag. Provide the Class of the Fragment
* it will be instantiated by this object. Proivde _args for your Fragment.
*
* #param fm
* #param _tag
* #param _class
* #param _args
*/
public void addFragment(FragmentManager fm, String _tag, Class<?> _class, Bundle _args){
TabInfo info = new TabInfo(_tag, _class, _args);
Stack<TabInfo> tabStack = mTabStackMap.get(_tag);
Fragment frag = fm.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + mTabTags.indexOf(_tag));
if(frag instanceof SaveStateBundle){
Bundle b = new Bundle();
((SaveStateBundle) frag).onRemoveFragment(b);
tabStack.peek().args = b;
}
tabStack.add(info);
FragmentTransaction ft = fm.beginTransaction();
ft.remove(frag).commit();
notifyDataSetChanged();
}
/**
* Will pop the Fragment added to the Tab with the Tag _tag
*
* #param fm
* #param _tag
* #return
*/
public boolean popFragment(FragmentManager fm, String _tag){
Stack<TabInfo> tabStack = mTabStackMap.get(_tag);
if(tabStack.size()>1){
tabStack.pop();
Fragment frag = fm.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + mTabTags.indexOf(_tag));
FragmentTransaction ft = fm.beginTransaction();
ft.remove(frag).commit();
notifyDataSetChanged();
return true;
}
return false;
}
public boolean back(FragmentManager fm) {
int position = mViewPager.getCurrentItem();
return popFragment(fm, mTabTags.get(position));
}
#Override
public int getCount() {
return mTabStackMap.size();
}
#Override
public int getItemPosition(Object object) {
ArrayList<Class<?>> positionNoneHack = new ArrayList<Class<?>>();
for(Stack<TabInfo> tabStack: mTabStackMap.values()){
positionNoneHack.add(tabStack.peek().clss);
} // if the object class lies on top of our stacks, we return default
if(positionNoneHack.contains(object.getClass())){
return POSITION_UNCHANGED;
}
return POSITION_NONE;
}
#Override
public Fragment getItem(int position) {
Stack<TabInfo> tabStack = mTabStackMap.get(mTabTags.get(position));
TabInfo info = tabStack.peek();
return Fragment.instantiate(mContext, info.clss.getName(), info.args);
}
#Override
public void onTabChanged(String tabId) {
int position = mTabHost.getCurrentTab();
mViewPager.setCurrentItem(position);
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
// Unfortunately when TabHost changes the current tab, it kindly
// also takes care of putting focus on it when not in touch mode.
// The jerk.
// This hack tries to prevent this from pulling focus out of our
// ViewPager.
TabWidget widget = mTabHost.getTabWidget();
int oldFocusability = widget.getDescendantFocusability();
widget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
mTabHost.setCurrentTab(position);
widget.setDescendantFocusability(oldFocusability);
}
#Override
public void onPageScrollStateChanged(int state) {
}
}
Add this for back button functionality in your MainActivity:
#Override
public void onBackPressed() {
if (!mTabsAdapter.back(getSupportFragmentManager())) {
super.onBackPressed();
}
}
If u like to save the Fragment State when it get's removed. Let your Fragment implement the interface SaveStateBundle return in the function a bundle with your save state. Get the bundle after instantiation by this.getArguments().
You can instantiate a tab like this:
mTabsAdapter.addTab(mTabHost.newTabSpec("firstTabTag").setIndicator("First Tab Title"),
FirstFragmentActivity.FirstFragmentFragment.class, null);
works similiar if u want to add a Fragment on top of a Tab Stack.
Important: I think, it won't work if u want to have 2 instances of same class on top of two Tabs.
I did this solution quick together, so I can only share it without providing any experience with it.
Replacing fragments in a viewpager is quite involved but is very possible and can look super slick. First, you need to let the viewpager itself handle the removing and adding of the fragments. What is happening is when you replace the fragment inside of SearchFragment, your viewpager retains its fragment views. So you end up with a blank page because the SearchFragment gets removed when you try to replace it.
The solution is to create a listener inside of your viewpager that will handle changes made outside of it so first add this code to the bottom of your adapter.
public interface nextFragmentListener {
public void fragment0Changed(String newFragmentIdentification);
}
Then you need to create a private class in your viewpager that becomes a listener for when you want to change your fragment. For example you could add something like this. Notice that it implements the interface that was just created. So whenever you call this method, it will run the code inside of the class below.
private final class fragmentChangeListener implements nextFragmentListener {
#Override
public void fragment0Changed(String fragment) {
//I will explain the purpose of fragment0 in a moment
fragment0 = fragment;
manager.beginTransaction().remove(fragAt0).commit();
switch (fragment){
case "searchFragment":
fragAt0 = SearchFragment.newInstance(listener);
break;
case "searchResultFragment":
fragAt0 = Fragment_Table.newInstance(listener);
break;
}
notifyDataSetChanged();
}
There are two main things to point out here:
fragAt0 is a "flexible" fragment. It can take on whatever fragment type you give it. This allows it to become your best friend in changing the fragment at position 0 to the fragment you desire.
Notice the listeners that are placed in the 'newInstance(listener)constructor. These are how you will callfragment0Changed(String newFragmentIdentification)`. The following code shows how you create the listener inside of your fragment.
static nextFragmentListener listenerSearch;
public static Fragment_Journals newInstance(nextFragmentListener listener){
listenerSearch = listener;
return new Fragment_Journals();
}
You could call the change inside of your onPostExecute
private class SearchAsyncTask extends AsyncTask<Void, Void, Void>{
protected Void doInBackground(Void... params){
.
.//some more operation
.
}
protected void onPostExecute(Void param){
listenerSearch.fragment0Changed("searchResultFragment");
}
}
This would trigger the code inside of your viewpager to switch your fragment at position zero fragAt0 to become a new searchResultFragment. There are two more small pieces you would need to add to the viewpager before it became functional.
One would be in the getItem override method of the viewpager.
#Override
public Fragment getItem(int index) {
switch (index) {
case 0:
//this is where it will "remember" which fragment you have just selected. the key is to set a static String fragment at the top of your page that will hold the position that you had just selected.
if(fragAt0 == null){
switch(fragment0){
case "searchFragment":
fragAt0 = FragmentSearch.newInstance(listener);
break;
case "searchResultsFragment":
fragAt0 = FragmentSearchResults.newInstance(listener);
break;
}
}
return fragAt0;
case 1:
// Games fragment activity
return new CreateFragment();
}
Now without this final piece you would still get a blank page. Kind of lame, but it is an essential part of the viewPager. You must override the getItemPosition method of the viewpager. Ordinarily this method will return POSITION_UNCHANGED which tells the viewpager to keep everything the same and so getItem will never get called to place the new fragment on the page. Here's an example of something you could do
public int getItemPosition(Object object)
{
//object is the current fragment displayed at position 0.
if(object instanceof SearchFragment && fragAt0 instanceof SearchResultFragment){
return POSITION_NONE;
//this condition is for when you press back
}else if{(object instanceof SearchResultFragment && fragAt0 instanceof SearchFragment){
return POSITION_NONE;
}
return POSITION_UNCHANGED
}
Like I said, the code gets very involved, but you basically have to create a custom adapter for your situation. The things I mentioned will make it possible to change the fragment. It will likely take a long time to soak everything in so I would be patient, but it will all make sense. It is totally worth taking the time because it can make a really slick looking application.
Here's the nugget for handling the back button. You put this inside your MainActivity
public void onBackPressed() {
if(mViewPager.getCurrentItem() == 0) {
if(pagerAdapter.getItem(0) instanceof FragmentSearchResults){
((Fragment_Table) pagerAdapter.getItem(0)).backPressed();
}else if (pagerAdapter.getItem(0) instanceof FragmentSearch) {
finish();
}
}
You will need to create a method called backPressed() inside of FragmentSearchResults that calls fragment0changed. This in tandem with the code I showed before will handle pressing the back button. Good luck with your code to change the viewpager. It takes a lot of work, and as far as I have found, there aren't any quick adaptations. Like I said, you are basically creating a custom viewpager adapter, and letting it handle all of the necessary changes using listeners
This is my way to achieve that.
First of all add Root_fragment inside viewPager tab in which you want to implement button click fragment event. Example;
#Override
public Fragment getItem(int position) {
if(position==0)
return RootTabFragment.newInstance();
else
return SecondPagerFragment.newInstance();
}
First of all, RootTabFragment should be include FragmentLayout for fragment change.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/root_frame"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
Then, inside RootTabFragment onCreateView, implement fragmentChange for your FirstPagerFragment
getChildFragmentManager().beginTransaction().replace(R.id.root_frame, FirstPagerFragment.newInstance()).commit();
After that, implement onClick event for your button inside FirstPagerFragment and make fragment change like that again.
getChildFragmentManager().beginTransaction().replace(R.id.root_frame, NextFragment.newInstance()).commit();
Hope this will help you guy.
I found simple solution, which works fine even if you want add new fragments in the middle or replace current fragment. In my solution you should override getItemId() which should return unique id for each fragment. Not position as by default.
There is it:
public class DynamicPagerAdapter extends FragmentPagerAdapter {
private ArrayList<Page> mPages = new ArrayList<Page>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
public DynamicPagerAdapter(FragmentManager fm) {
super(fm);
}
public void replacePage(int position, Page page) {
mPages.set(position, page);
notifyDataSetChanged();
}
public void setPages(ArrayList<Page> pages) {
mPages = pages;
notifyDataSetChanged();
}
#Override
public Fragment getItem(int position) {
if (mPages.get(position).mPageType == PageType.FIRST) {
return FirstFragment.newInstance(mPages.get(position));
} else {
return SecondFragment.newInstance(mPages.get(position));
}
}
#Override
public int getCount() {
return mPages.size();
}
#Override
public long getItemId(int position) {
// return unique id
return mPages.get(position).getId();
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
while (mFragments.size() <= position) {
mFragments.add(null);
}
mFragments.set(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
mFragments.set(position, null);
}
#Override
public int getItemPosition(Object object) {
PagerFragment pagerFragment = (PagerFragment) object;
Page page = pagerFragment.getPage();
int position = mFragments.indexOf(pagerFragment);
if (page.equals(mPages.get(position))) {
return POSITION_UNCHANGED;
} else {
return POSITION_NONE;
}
}
}
Notice: In this example FirstFragment and SecondFragment extends abstract class PageFragment, which has method getPage().
I doing something to similar to wize but in my answer yo can change between the two fragments whenever you want. And with the wize answer I have some problems when changing the orientation of the screen an things like that. This is the PagerAdapter looks like:
public class MyAdapter extends FragmentPagerAdapter
{
static final int NUM_ITEMS = 2;
private final FragmentManager mFragmentManager;
private Fragment mFragmentAtPos0;
private Map<Integer, String> mFragmentTags;
private boolean isNextFragment=false;
public MyAdapter(FragmentManager fm)
{
super(fm);
mFragmentManager = fm;
mFragmentTags = new HashMap<Integer, String>();
}
#Override
public Fragment getItem(int position)
{
if (position == 0)
{
if (isPager) {
mFragmentAtPos0 = new FirstPageFragment();
} else {
mFragmentAtPos0 = new NextFragment();
}
return mFragmentAtPos0;
}
else
return SecondPageFragment.newInstance();
}
#Override
public int getCount()
{
return NUM_ITEMS;
}
#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 void onChange(boolean isNextFragment) {
if (mFragmentAtPos0 == null)
mFragmentAtPos0 = getFragment(0);
if (mFragmentAtPos0 != null)
mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();
if (!isNextFragment) {
mFragmentAtFlashcards = new FirstPageFragment();
} else {
mFragmentAtFlashcards = new NextFragment();
}
notifyDataSetChanged();
}
#Override
public int getItemPosition(Object object)
{
if (object instanceof FirstPageFragment && mFragmentAtPos0 instanceof NextFragment)
return POSITION_NONE;
if (object instanceof NextFragment && mFragmentAtPos0 instanceof FirstPageFragment)
return POSITION_NONE;
return POSITION_UNCHANGED;
}
public Fragment getFragment(int position) {
String tag = mFragmentTags.get(position);
if (tag == null)
return null;
return mFragmentManager.findFragmentByTag(tag);
}
}
The listener I implemented in the adapter container activity to put it to the fragment when attaching it, this is the activity:
public class PagerContainerActivity extends AppCompatActivity implements ChangeFragmentListener {
//...
#Override
public void onChange(boolean isNextFragment) {
if (pagerAdapter != null)
pagerAdapter.onChange(isNextFragment);
}
//...
}
Then in the fragment putting the listener when attach an calling it:
public class FirstPageFragment extends Fragment{
private ChangeFragmentListener changeFragmentListener;
//...
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
changeFragmentListener = ((PagerContainerActivity) activity);
}
#Override
public void onDetach() {
super.onDetach();
changeFragmentListener = null;
}
//...
//in the on click to change the fragment
changeFragmentListener.onChange(true);
//...
}
And finally the listener:
public interface changeFragmentListener {
void onChange(boolean isNextFragment);
}
I followed the answers by #wize and #mdelolmo and I got the solution. Thanks Tons. But, I tuned these solutions a little bit to improve the memory consumption.
Problems I observed:
They save the instance of Fragment which is replaced. In my case, it is a Fragment which holds MapView and I thought its costly. So, I am maintaining the FragmentPagerPositionChanged (POSITION_NONE or POSITION_UNCHANGED) instead of Fragment itself.
Here is my implementation.
public static class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter {
private SwitchFragListener mSwitchFragListener;
private Switch mToggle;
private int pagerAdapterPosChanged = POSITION_UNCHANGED;
private static final int TOGGLE_ENABLE_POS = 2;
public DemoCollectionPagerAdapter(FragmentManager fm, Switch toggle) {
super(fm);
mToggle = toggle;
mSwitchFragListener = new SwitchFragListener();
mToggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
mSwitchFragListener.onSwitchToNextFragment();
}
});
}
#Override
public Fragment getItem(int i) {
switch (i)
{
case TOGGLE_ENABLE_POS:
if(mToggle.isChecked())
{
return TabReplaceFragment.getInstance();
}else
{
return DemoTab2Fragment.getInstance(i);
}
default:
return DemoTabFragment.getInstance(i);
}
}
#Override
public int getCount() {
return 5;
}
#Override
public CharSequence getPageTitle(int position) {
return "Tab " + (position + 1);
}
#Override
public int getItemPosition(Object object) {
// This check make sures getItem() is called only for the required Fragment
if (object instanceof TabReplaceFragment
|| object instanceof DemoTab2Fragment)
return pagerAdapterPosChanged;
return POSITION_UNCHANGED;
}
/**
* Switch fragments Interface implementation
*/
private final class SwitchFragListener implements
SwitchFragInterface {
SwitchFragListener() {}
public void onSwitchToNextFragment() {
pagerAdapterPosChanged = POSITION_NONE;
notifyDataSetChanged();
}
}
/**
* Interface to switch frags
*/
private interface SwitchFragInterface{
void onSwitchToNextFragment();
}
}
Demo link here.. https://youtu.be/l_62uhKkLyM
For demo purpose, used 2 fragments TabReplaceFragment and DemoTab2Fragment at position two. In all the other cases I'm using DemoTabFragment instances.
Explanation:
I'm passing Switch from Activity to the DemoCollectionPagerAdapter. Based on the state of this switch we will display correct fragment. When the switch check is changed, I'm calling the SwitchFragListener's onSwitchToNextFragment method, where I'm changing the value of pagerAdapterPosChanged variable to POSITION_NONE. Check out more about POSITION_NONE. This will invalidate the getItem and I have logics to instantiate the right fragment over there. Sorry, if the explanation is a bit messy.
Once again big thanks to #wize and #mdelolmo for the original idea.
Hope this is helpful. :)
Let me know if this implementation has any flaws. That will be greatly helpful for my project.
after research i found solution with short code.
first of all create a public instance on fragment and just remove your fragment on onSaveInstanceState if fragment not recreating on orientation change.
#Override
public void onSaveInstanceState(Bundle outState) {
if (null != mCalFragment) {
FragmentTransaction bt = getChildFragmentManager().beginTransaction();
bt.remove(mFragment);
bt.commit();
}
super.onSaveInstanceState(outState);
}

Categories

Resources