Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a generic class for multiple objects (actionBar tabs)

Currently I use ABS, ActionBar Tabs and a TabsAdapter/ViewPager to make a nice tab layout for my app. I have 5+ category tabs up top - eventually the user will be able to add new categories (I'll set this up later) as well. So, at the moment, I have a main SherlockFragmentActivity with many SherlockFragment category files. In onCreate for the main SFA, I build the actionBar and add all its tabs, like so:

mTabsAdapter.addTab(bar.newTab().setText(R.string.login),
            LoginFragment.class, null);
    mTabsAdapter.addTab(bar.newTab().setText("Geographics"),
            GeoFragment.class, null);
    mTabsAdapter.addTab(bar.newTab().setText(R.string.economics),
            EconFragment.class, null);
    mTabsAdapter.addTab(bar.newTab().setText(R.string.elections),
            ElectionsFragment.class, null);

What I would like to do instead, is create a new solution, CategoryFragment rather than using all of the specific Elections, Geo, Econ etc. Can anyone imagine a solution? Ideally, I would like to just pass a string to the tab that gets added, so that CategoryFragment can just inflate based on the string. I would like this solution because the code is very redundant in multiple classes, when all the class really does is load stuff from an SQL dB online, getting only the data for its own category.

Here is my TabsAdapter class:

public class TabsAdapter extends FragmentPagerAdapter
implements ActionBar.TabListener, ViewPager.OnPageChangeListener {
    private final Context mContext;
    private Polling activity;
    private final ActionBar mActionBar;
    private final ViewPager mViewPager;
    private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();

    final class TabInfo {
        private final Class<?> clss;
        private final Bundle args;
        //private final String title;
                    //This string is implemented only as part of my attempt!

        TabInfo(Class<?> _class, Bundle _args, String _title) {
            clss = _class;
            args = _args;
            title = _title;
        }
    }
    /*Constructor method that adds a TabsAdapter to each tab that is created.
     * It also adds the ViewPager to each tab so that the user can swipe to change tabs.
     */
    public TabsAdapter(SherlockFragmentActivity activity, ViewPager pager) {
        super(activity.getSupportFragmentManager());
        mContext = activity;
        this.activity = (Polling) activity;
        mActionBar = activity.getSupportActionBar();
        mViewPager = pager;
        mViewPager.setAdapter(this);
        mViewPager.setOnPageChangeListener(this);
    }

    /*This is the method I've been trying to use to solve the problem, but it's not really cutting it!*/

    public void buildTabs() throws ClassNotFoundException { 
        String [] tabs = {"Econ", "Elections", "Geo", "Politics", "Science", "Finance", "Religion", 
                "Military", "International" };
        final String resource = "R.string.";            
        mTabsAdapter.addTab(bar.newTab().setText("Login"),
                LoginFragment.class, null, "Login");
        for (int j = 0; j < tabs.length; j++) {
            String res = resource + tabs[j];
            String clas = tabs[j] + "Fragment";
            String total = "com.davekelley.polling." + clas;
        mTabsAdapter.addTab(bar.newTab().setText(tabs[j]),
                CategoryFragment.class, null, tabs[j]);
        }           
    }

    /*A fairly simple method that sets the TabInfo for each tab so that the TabsAdapter
     * knows which class the tab that is being added actually belonds to. It also updates
     * the UI interface when each tab is added. 
     */

    public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args, String title) {
        TabInfo info = new TabInfo(clss, args, title);
        tab.setTag(info);
        tab.setTabListener(this);
        mTabs.add(info);
        mActionBar.addTab(tab);
        notifyDataSetChanged();
    }   

    public int getCount() {
        return mTabs.size();
    }
    /*A method that is used in other classes to allow each tab Fragment to 
     * access its inherited methods from a mother-class, in this case, SherlockFragment
     */

    public int getPosition(SherlockFragment fragment) {
        for (int j = 1; j < mTabs.size(); j++) {
            TabInfo info = (TabInfo) mActionBar.getTabAt(j).getTag();
            if (info.title.matches(mTabs.get(j).title)) {
                return j;
            }
        }
        return -1;


    }
    public SherlockFragment getItem(int position) {
        TabInfo info = mTabs.get(position);
        return (SherlockFragment)Fragment.instantiate(mContext, info.clss.getName(), info.args);
    }
    /*This method reads the user's selection for a new tab and sets that tab as
     * the new current focus.*/
    public void onPageSelected(int position) {
        mActionBar.setSelectedNavigationItem(position);
        selectInSpinnerIfPresent(position, true);
    }

    private void selectInSpinnerIfPresent(int position, boolean animate) {
        try {
            View actionBarView = findViewById(R.id.abs__action_bar);
            if (actionBarView == null) {
                int id = getResources().getIdentifier("action_bar", "id", "android");
                actionBarView = findViewById(id);
            }

            Class<?> actionBarViewClass = actionBarView.getClass();
            Field mTabScrollViewField = actionBarViewClass.getDeclaredField("mTabScrollView");
            mTabScrollViewField.setAccessible(true);

            Object mTabScrollView = mTabScrollViewField.get(actionBarView);
            if (mTabScrollView == null) {
                return;
            }

            Field mTabSpinnerField = mTabScrollView.getClass().getDeclaredField("mTabSpinner");
            mTabSpinnerField.setAccessible(true);

            Object mTabSpinner = mTabSpinnerField.get(mTabScrollView);
            if (mTabSpinner == null) {
                return;
            }

            Method setSelectionMethod = mTabSpinner.getClass().getSuperclass().getDeclaredMethod("setSelection", Integer.TYPE, Boolean.TYPE);
            setSelectionMethod.invoke(mTabSpinner, position, animate);

        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    public void onPageScrollStateChanged(int state) {}
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
    /* This is the method that actually draws the newest tab onto the screen when
     * it is selected.*/
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        mViewPager.setCurrentItem(tab.getPosition());

        sp = getSharedPreferences("prefs", MODE_PRIVATE);
        SharedPreferences.Editor preferencesEditor = sp.edit();
        preferencesEditor.putInt("lastPosition", mViewPager.getCurrentItem());
        preferencesEditor.commit();
    }
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {}
    public void onTabReselected(Tab tab, FragmentTransaction ft) {}
    public void onTabReselected(Tab tab, android.app.FragmentTransaction ft) {}
    public void onTabUnselected(Tab tab, android.app.FragmentTransaction ft) {}
}
like image 942
Davek804 Avatar asked Jun 04 '12 22:06

Davek804


1 Answers

Well, you want to use one Fragment class, say BaseFragment, instead of multiple classes; Doing so by passing a string (identifier) for this new Fragment. You also have some code run in all of these Fragments so it would be preferable to use the new approach.

However, passing an identifier to this BaseFragment will only cause its code to be messy and you will have to handle all these if...else, when it really is all related to this identifier and your code is actually still the same because you will have to move the code of Specific fragments into this BaseFragment. Hence, there is no added value in this approach. You can keep all your fragments, and actually use this "identifier" when you are adding the Tabs to choose the right Fragment to add.


As for your code that is run in many of your fragments. This is a very basic approach:

Create a class BaseFragment that extends Fragment. (Am being naive here) Add a function common() that will do this common thing. Finally let all your Fragments extend this BaseFragment instead of Fragment. Now, you can call common(). (Again, you might need common1(), common2(),...).

like image 173
Sherif elKhatib Avatar answered Sep 29 '22 17:09

Sherif elKhatib