Kartik_Koro
Kartik_Koro

Reputation: 1287

Android: Replacing a fragment in a view pager

I have two fragments SearchFragment and CreateFragment in a view pager inside a activity called TicketManagementActivity. Now when the user presses the search button in SearchFragment, I want SearchFragment to be replaced with SearchResultFragment. I should then be able to swipe between SeachResultFragment and CreateFragment in the ViewPager. Also when I press back from SearchResultFragment I should go back to SearchFragment.

Right now, when I press the button I get a blank screen instead of the layout of SearchResultFragment. When I press back I get to SearchFragment but now I have to click the button twice for the blank screen to come. Now after the blank screen comes after the double click, whenever I swipe to CreateFragment tab I get a blank screen instead of CreateFragment layout.

I looked at quite a number of questions on SO but none of them seem to be working for me. Most useful seems to be the first two answers in this question, but the first answer doesn't handle the back press, nor am I able to implement it. The second answer seems very implementable but I get errors which I have mentioned below.

My main TicketManagemementActivity:

public class TicketManagementActivity extends FragmentActivity implements
ActionBar.TabListener {

    ViewPager viewPager;
    TabsPagerAdapter adapter;
    ActionBar actionBar;

    String[] tabs={"Search", "Create"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ticket_management);

        viewPager=(ViewPager)findViewById(R.id.pager);
        actionBar=getActionBar();
        adapter=new TabsPagerAdapter(getSupportFragmentManager());

        viewPager.setAdapter(adapter);
        actionBar.setHomeButtonEnabled(false);
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        for(String tab_name : tabs){
            actionBar.addTab(actionBar.newTab().setText(tab_name).setTabListener(this));
        }
        viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

            @Override
            public void onPageSelected(int position) {
                // on changing the page
                // make respected tab selected
                actionBar.setSelectedNavigationItem(position);
            }

            @Override
            public void onPageScrolled(int arg0, float arg1, int arg2) {
            }

            @Override
            public void onPageScrollStateChanged(int arg0) {
            }
        });

    }
  //removed methods for menu creation and filling and placeholder fragment for brevity on SO
    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {

        viewPager.setCurrentItem(tab.getPosition());
    }

    @Override
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {


    }

    @Override
    public void onTabReselected(Tab tab, FragmentTransaction ft) {


    }

}

My activity_ticket_management.xml which is layout set in onCreate of ticket management activity, just contains the viewpager

<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="match_parent">
</android.support.v4.view.ViewPager>

My TabsPagerAdapter class extending FragmentPagerAdapter:

public class TabsPagerAdapter extends FragmentPagerAdapter {

    public TabsPagerAdapter(android.support.v4.app.FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int index) {

        switch (index) {
        case 0:
            // Top Rated fragment activity
            return new SearchFragment();
        case 1:
            // Games fragment activity
            return new CreateFragment();

        }

        return null;
    }

    @Override
    public int getCount() {
        // get item count - equal to number of tabs
        return 2;
    }

}

Relevant part of my SearchFragment:

public class SearchFragment extends Fragment implements View.OnClickListener    {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        View rootView = inflater.inflate(R.layout.fragment_search, container, false);
        .
        .//some widget initializations
        .
        return rootView;
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.ticket_search_btn: searchSigmaTickets();
                                     break;
        }
    }

    public void searchSigmaTickets(){
        .
        .
        .
        .//some operations
        .

        new SearchAsyncTask().execute();

        }
    }

    private class SearchAsyncTask extends AsyncTask<Void, Void, Void>{

        protected Void doInBackground(Void... params){
            .
            .//some more operation
            .
        }
        protected void onPostExecute(Void param){
            Fragment newFragment = new SearchResultFragment();
            //Here I use getFragmentManager and not getChildFragmentManager
            FragmentTransaction transaction = getFragmentManager().beginTransaction();
            //HERE I try to replace the fragment. I'm not sure what id to pass, I pass the id of the main veiwpager in ticketmanagement activity
            transaction.replace(R.id.pager, newFragment);
            transaction.addToBackStack(null);
            transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);

            transaction.commit();
        }

    }

}

If I use getChildFragmentManager instead of getFragmentManager as mentioned in the second answer I get

06-25 06:55:32.045: E/AndroidRuntime(2797): java.lang.IllegalArgumentException: No view found for id 0x7f06003c (com.amberroad.sigmaticket:id/pager) for fragment SearchResultFragment{b2fed358 #0 id=0x7f06003c}

Sorry for the lengthy question, how should I solve this?

Upvotes: 1

Views: 1996

Answers (1)

user3331142
user3331142

Reputation: 1262

Kartik, get ready for a lengthy answer to your lenghty question. 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: 1)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. 2) 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 then 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){
            ((FragmentSearchResults) 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

Here is the code all together for the TabsPagerAdapter.

public class TabsPagerAdapter extends FragmentPagerAdapter{

Fragment fragAt0;
fragmentChangeListener listener = new fragmentChangeListener();
FragmentManager manager;

static String fragment0 = "SearchFragment";

//when you declare the viewpager in your adapter, pass it the fragment manager.
public viewPager(FragmentManager fm) {
    super(fm);
    manager = fm;

}

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

   @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();

}

@Override
public int getCount() {
    // Show 3 total pages.
    return 3;
}

@Override
public CharSequence getPageTitle(int position) {
    Locale l = Locale.getDefault();
    String[] tab = {"Journals", "Charts", "Website"};
    switch (position) {
        case 0:
            return tab[0].toUpperCase(l);
        case 1:
            return tab[1].toUpperCase(l);
        case 2:
            return tab[2].toUpperCase(l);
    }
    return null;
}

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

}

public interface nextFragmentListener {
    public void fragment0Changed(String fragment);

}

Upvotes: 1

Related Questions