Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android: ActionBar, TabListener and support.v4.Fragment

I'm building an app with MainActivity which extends FragmentActivity and has an ActionBar with three tabs. At each tab I would like to attach a ParentFragment which should control the swipe among internal pages. The result should be that pressing the tabs the user should change from the different instances of the ParentFragment and swiping he should change among the TextViewFragments inside each ParentFragment.
Now I can perfectly switch among the three tabs, but the problem is that the ParentFragment is not correctly shown, it seems it's not even created because I can't even see the onCreate(), onCreateView() and onViewCreated() functions in the LogCat view.
I think that the problem resides in the custom TabListener class and its functions but I can't really understand what the problem is. I tried to search on many sites and I've looked into a lot of examples but none of them was related to the same situation.
What do I miss or what am I doing wrong here?

This is my MainActivity:

    import java.util.Random;
    import android.app.ActionBar;
    import android.app.ActionBar.Tab;
    import android.graphics.drawable.Drawable;
    import android.os.Bundle;
    import android.support.v4.app.Fragment;
    import android.support.v4.app.FragmentActivity;
    import android.support.v4.app.FragmentTransaction;
    import android.support.v4.view.ViewPager;
    import android.view.Gravity;
    import android.view.LayoutInflater;
    import android.view.Menu;
    import android.view.MenuItem;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.TextView;
    import android.widget.Toast;
    import com.viewpagerindicator.PageIndicator;

public class MainActivity extends FragmentActivity {

    MyAdapter mAdapter;
    ViewPager mPager;
    PageIndicator mIndicator;    
    private View mCustomView; 
    private static final Random RANDOM = new Random();

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

        // Set up the action bar.
        ActionBar.LayoutParams params = new ActionBar.LayoutParams(
                ActionBar.LayoutParams.MATCH_PARENT,
                ActionBar.LayoutParams.MATCH_PARENT, Gravity.CENTER);
        final ActionBar actionBar = getActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
        actionBar.setDisplayShowTitleEnabled(false);
        actionBar.setDisplayUseLogoEnabled(false);
        actionBar.setDisplayShowHomeEnabled(false);


            actionBar.addTab(actionBar
                    .newTab()
                    .setIcon(getIcon(0, true))
                    .setTabListener(
                            new TabListener<ParentFragment>(this, "home",
                                    ParentFragment.class)));

            actionBar.addTab(actionBar
                    .newTab()
                    .setIcon(getIcon(1, false))
                    .setTabListener(
                            new TabListener<ParentFragment>(this, "apps",
                                    ParentFragment.class)));

            actionBar.addTab(actionBar
                    .newTab()
                    .setIcon(getIcon(2, false))
                    .setTabListener(
                            new TabListener<ParentFragment>(this, "settings",
                                    ParentFragment.class)));

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

    }


    public static class TabListener<T extends Fragment> implements
            ActionBar.TabListener {

        private Fragment mFragment;
        private final FragmentActivity mActivity;
        private final String mTag;
        private final Class<T> mClass;
        private final Bundle mArgs;
        private FragmentTransaction fft;
            public static final String TAG = TabListener.class.getSimpleName();

        public TabListener(FragmentActivity activity, String tag, Class<T> clz) {

            this(activity, tag, clz, null);
        }

        public TabListener(FragmentActivity activity, String tag, Class<T> clz,
                Bundle args) {

            mActivity = activity;
            mTag = tag;
            mClass = clz;
            mArgs = args;

            mFragment = mActivity.getSupportFragmentManager()
                    .findFragmentByTag(mTag);
            if (mFragment != null && !mFragment.isDetached()) {

                fft = mActivity.getSupportFragmentManager().beginTransaction();
                fft.detach(mFragment);
                fft.commit();
            }
        }

        public void onTabSelected(Tab tab, android.app.FragmentTransaction ft) {

            System.out.println(TAG + (" - ") + " -> mTag "
                    + mTag + " selected!");
            fft = mActivity.getSupportFragmentManager().beginTransaction();

            if (mFragment == null) {
                mFragment = Fragment.instantiate(mActivity, mClass.getName());

                fft.add(android.R.id.content, mFragment, mTag);

            } else {
                System.out.println(TAG + (" - ") + " -> Fragment con mTag = " + mTag
                        + " trovato con tag!");
                fft.attach(mFragment);
                fft.commit();
            }
        }

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

        }

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

            mFragment = mActivity.getSupportFragmentManager()
                    .findFragmentByTag(mTag);
            System.out.println(TAG + (" - ") + " -> mTag "
                    + mTag + " unselected!");

            if (mFragment != null && !mFragment.isDetached()) {
                fft = mActivity.getSupportFragmentManager().beginTransaction();
                fft.detach(mFragment);
                fft.commit();
            }
        }

    }

    public Drawable getIcon(int tabPosition, boolean selected) {

        switch (tabPosition) {
        case 0:
            return getResources().getDrawable(R.drawable.actionbar_home);

        case 1:
            return getResources().getDrawable(R.drawable.actionbar_apps);

        case 2:
            return getResources().getDrawable(R.drawable.actionbar_settings);

        }
        return null;
    }

}

And this is my ParentFragment which exteds now the android.support.v4.app.Fragment class:

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.viewpagerindicator.IconPageIndicator;
import com.viewpagerindicator.PageIndicator;

public class ParentFragment extends Fragment {

    public static final String TAG = ParentFragment.class.getSimpleName();

    MyAdapter fragmentAdapter;
    ViewPager mPager;
    PageIndicator mIndicator;



    public static ParentFragment newInstance() {
 System.out.println(TAG + " - newInstance()");
    System.out.println(TAG + " - " + methodName());
        ParentFragment fragment = new ParentFragment();
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
System.out.println(TAG + " - onCreate()");
        System.out.println(TAG + " - " + methodName());

        super.onCreate(savedInstanceState);
        setRetainInstance(true);

    }

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

System.out.println(TAG + " - onCreateView()");
        System.out.println(TAG + " - " + methodName());
        fragmentAdapter = new MyAdapter(getFragmentManager());

        View view = inflater.inflate(R.layout.simple_icons, container, false);
        mPager = (ViewPager) view.findViewById(R.id.pager);
        mPager.setAdapter(fragmentAdapter);

        return view;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        System.out.println(TAG + " - onViewCreated()" );
        System.out.println(TAG + " - " + methodName());
        super.onViewCreated(view, savedInstanceState);

        mIndicator = (IconPageIndicator) getActivity().findViewById(R.id.indicator);
        mIndicator.setViewPager(mPager);          
    }
}

This is the MyAdapter class:

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.PagerAdapter;
import android.view.View;
import android.view.ViewGroup;
import com.viewpagerindicator.IconPagerAdapter;

public class MyAdapter extends PagerAdapter  {

    FragmentManager myFragmentMgr;
    private FragmentTransaction mCurTransaction;

    protected static final String[] CONTENT = new String[] { "This", "Is", "A", "Test", };
    protected static final int[] ICONS = new int[] {
            R.drawable.icon_page_indicator,
            R.drawable.icon_page_indicator,
            R.drawable.icon_page_indicator,
            R.drawable.perm_group_calendar,
            R.drawable.perm_group_camera,
            R.drawable.perm_group_device_alarms,
            R.drawable.perm_group_location
    };

    private int mCount = CONTENT.length;

    public MyAdapter(FragmentManager fm) {
        super();
        myFragmentMgr = fm;
    }

    @Override
    public int getCount() {

        return 3;
    }

    @Override
    public Object instantiateItem(ViewGroup viewGroup, int position) {

        ParentFragment fragment = ParentFragment.newInstance(); 
        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup viewGroup, int position, Object obj) {

        if (mCurTransaction == null) {
            mCurTransaction = myFragmentMgr.beginTransaction();
        }
        mCurTransaction.detach((Fragment)obj);
    }   

    @Override
    public boolean isViewFromObject(View arg0, Object arg1) {
        return false;
    }

    public Fragment getItem(int position) {
        Bundle args = new Bundle();
        args.putInt(TextViewFragment.POSITION_KEY, position);
        return TextViewFragment.newInstance(args);
    }

    @Override
    public CharSequence getPageTitle(int position) {

        return "Fragment # " + position;
    }

    public int getIconResId(int index) {

        return ICONS[index % ICONS.length];
    }

     public void setCount(int count) {
            if (count > 0 && count <= 10) {
                mCount = count;
                notifyDataSetChanged();
            }
        } 
}

and the last the TextViewFragment class:

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

public class TextViewFragment extends Fragment {

    public static final String POSITION_KEY = "POSITION";
    public static final String TITLE_KEY = "TITLE";

    public static TextViewFragment newInstance(Bundle args) {
        TextViewFragment fragment = new TextViewFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_test, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        TextView textview = (TextView) view.findViewById(R.id.textViewPosition);
        final int position;
        if (getArguments() != null) {
            position = getArguments().getInt(POSITION_KEY);
            textview.setText(Integer.toString(position));
        } else {
            position = 0;
            textview.setText("Child Fragment");
        }

        textview.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {

                Toast.makeText(v.getContext(), "Clicked Position: " + position,
                        Toast.LENGTH_LONG).show();
            }
        });
    }    
}

----- EDIT: SOLUTION -----

I cannot answer my own question due to "low reputation" but I share anyway the solution, in order to help someone else and hopefully to recover some votes.

I finally found the way to make things work for me, I hope this could be helpful for anyone else experiencing the same problem. As I suspected, the problem was in TabListener. My problem was solved changing that part with this code:

   public static class TabListener<T extends Fragment> implements
            ActionBar.TabListener {
    private Fragment mFragment;
    private final FragmentActivity mActivity;
    private final String mTag;
    private final Class<T> mClass;
    private final Bundle mArgs;
    private FragmentTransaction fft;

    public TabListener(FragmentActivity activity, String tag, Class<T> clz) {

        this(activity, tag, clz, null);
    }

    public TabListener(FragmentActivity activity, String tag, Class<T> clz,
            Bundle args) {

        mActivity = activity;
        mTag = tag;
        mClass = clz;
        mArgs = args;

        System.out.println(TAG + " - " + methodName() + " - Constructor of TabListener -> mTag = "
                + mTag);
        // Check to see if we already have a fragment for this tab, probably
        // from a previously saved state. If so, deactivate it, because our
        // initial state is that a tab isn't shown.
        mFragment = mActivity.getSupportFragmentManager()
                .findFragmentByTag(mTag);
        if (mFragment != null && !mFragment.isDetached()) {

            fft = mActivity.getSupportFragmentManager().beginTransaction();
            fft.detach(mFragment);
            fft.commit();
            mActivity.getSupportFragmentManager()
                    .executePendingTransactions();
        }
    }

    public void onTabSelected(Tab tab, android.app.FragmentTransaction ft) {

        Fragment tmp = mActivity.getSupportFragmentManager()
                .findFragmentByTag(mTag);

        fft = mActivity.getSupportFragmentManager().beginTransaction();

        if (mFragment == null) {

            System.out.println(TAG + " - " + methodName() + " - add mFragment " + mTag);
            // If not, instantiate and add it to the activity
            mFragment = Fragment.instantiate(mActivity, mClass.getName());
            fft.add(android.R.id.content, mFragment, mTag);
            fft.commit();
            mActivity.getSupportFragmentManager()
                    .executePendingTransactions();
        } else {
            System.out.println(TAG + " - " + methodName() + " - attach mFragment " + mTag);
            // If it exists, simply attach it in order to show it
            fft.attach(mFragment);
            fft.commit();
            mActivity.getSupportFragmentManager()
                    .executePendingTransactions();
        }
    }

    @Override
    public void onTabReselected(Tab tab, android.app.FragmentTransaction ft) {
        System.out.println(TAG + " - " + methodName() + " - mFragment " + mTag);

    }

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

        mFragment = mActivity.getSupportFragmentManager()
                .findFragmentByTag(mTag);

        if (mFragment != null && !mFragment.isDetached()) {
            System.out.println(TAG + " - " + methodName() + " - detach mFragment " + mTag);
            fft = mActivity.getSupportFragmentManager().beginTransaction();
            fft.detach(mFragment);
            fft.commit();
            mActivity.getSupportFragmentManager()
                    .executePendingTransactions();
        }
    }



    public static final String TAG = TabListener.class.getSimpleName();
    private static final int CLIENT_CODE_STACK_INDEX;

    static {
        // Finds out the index of "this code" in the returned stack trace -
        // funny but it differs in JDK 1.5 and 1.6
        int i = 0;
        for (StackTraceElement ste : Thread.currentThread().getStackTrace()) {
            i++;
            if (ste.getClassName().equals(TabListener.class.getName())) {
                break;
            }
        }
        CLIENT_CODE_STACK_INDEX = i;
    }

    public static String methodName() {

        if (Thread.currentThread().getStackTrace().length < CLIENT_CODE_STACK_INDEX)
            return Thread.currentThread().getStackTrace()[CLIENT_CODE_STACK_INDEX]
                    .getMethodName() + "()";
        else
            return new String(
                    "methodName() - stackTrace index is out of bound");
    }
}

The fundamental difference was the addition after each commit() of the following line:

mActivity.getSupportFragmentManager().executePendingTransactions();

This problem was really poorly documented and probably happens only in particular conditions, but I hope this solution saves some time to anyone else having the same issues!

like image 851
Andre Avatar asked Jun 03 '13 12:06

Andre


1 Answers

Thanks for the answer, I had this exact same problem with the support library where the fragments will be overlaid ontop of each other when i tried to switch between tabs. There were a couple of unused variables in the solution so I cleaned it up a bit:

public static class TabListener<T extends Fragment> implements ActionBar.TabListener {

  private Fragment mFragment;
  private final FragmentActivity mActivity;
  private final String mTag;
  private final Class<T> mClass;
  private FragmentTransaction fft;

  public TabListener(FragmentActivity activity, String tag, Class<T> clz) {

      mActivity = activity;
      mTag = tag;
      mClass = clz;

      // Check to see if we already have a fragment for this tab, probably
      // from a previously saved state. If so, deactivate it, because our
      // initial state is that a tab isn't shown.
      mFragment = mActivity.getSupportFragmentManager().findFragmentByTag(mTag);
      if (mFragment != null && !mFragment.isDetached()) {

          fft = mActivity.getSupportFragmentManager().beginTransaction();
          fft.detach(mFragment);
          fft.commit();
          mActivity.getSupportFragmentManager()
          .executePendingTransactions();
      }
  }

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

      mFragment = mActivity.getSupportFragmentManager().findFragmentByTag(mTag);
      fft = mActivity.getSupportFragmentManager().beginTransaction();

      if (mFragment == null) {

          // If not, instantiate and add it to the activity
          mFragment = Fragment.instantiate(mActivity, mClass.getName());
          ft.add(R.id.content_view, mFragment, mTag);
          //fft.add(android.R.id.content, mFragment, mTag);
          fft.commit();
          mActivity.getSupportFragmentManager().executePendingTransactions();
      } else {

          // If it exists, simply attach it in order to show it
          fft.attach(mFragment);
          fft.commit();
          mActivity.getSupportFragmentManager().executePendingTransactions();
      }
  }

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

  }

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

      mFragment = mActivity.getSupportFragmentManager().findFragmentByTag(mTag);

      if (mFragment != null && !mFragment.isDetached()) {
          fft = mActivity.getSupportFragmentManager().beginTransaction();
          fft.detach(mFragment);
          fft.commit();
          mActivity.getSupportFragmentManager().executePendingTransactions();
      }
  }

}

like image 153
doubleristretto Avatar answered Nov 22 '22 20:11

doubleristretto