3

I have a tabbed application built with fragments and ActionBarSherlock. I have 7 tabs. Here's what's happening.

When I select any tab, the onCreate method for the associated fragment is called as expected. The problem is that the onCreate method is called for the next adjacent tab as well. For instance:

  1. App starts in tab1 and onCreate is called as expected
  2. tab2 onCreate is called as well (should not happen)
  3. -------
  4. Click on tab2 and onCreate is called as expected (even though it's already been called)
  5. tab3 onCreate is called as well (should not happen)
  6. -------
  7. Click on tab6 and onCreate is called as expected
  8. tab7 onCreate is called as well (should not happen)
  9. -------
  10. And really weird, click on tab7 (the last tab)
  11. tab6 (2nd to last tab) onCreate is called as well (should not happen)

I've read a couple of possible problems and checked to make sure it's not happening here:

  1. Not using unique tag for each tab (they are unique)
  2. Emulator has bug that calls onCreate twice (I get the same behavior on my ICS device)

So it's not the two previous possibilities and I'll completely out of ideas. The program runs fine but loading two fragments (which are webviews) takes too much time and isn't the behavior I expected.

Here's my code for the main activity onCreate which creates the tab host: EDITED:

public class SynergyWorldwideActivity extends SherlockFragmentActivity
{
//TabHost mTabHost;
ViewPager  mViewPager;
TabsAdapter mTabsAdapter;

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

    // Set up the view pager
    setContentView(R.layout.fragment_tabs_pager);
    mViewPager = (ViewPager)findViewById(R.id.pager);

    // Set up action bar
    final ActionBar bar = getSupportActionBar();
    bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    bar.setDisplayShowTitleEnabled(true);
    //bar.setDisplayShowHomeEnabled(false);

    // Creat tabs with bundled URLs
    Bundle  tab1Args=new Bundle(), tab2Args=new Bundle(), tab3Args=new Bundle(),
            tab4Args=new Bundle(), tab5Args=new Bundle(), tab6Args=new Bundle(),        tab7Args=new Bundle();
    tab1Args.putString("tabURL", getString(R.string.webtab1_URL));
    tab2Args.putString("tabURL", getString(R.string.webtab2_URL));
    tab3Args.putString("tabURL", getString(R.string.webtab3_URL));
    tab4Args.putString("tabURL", getString(R.string.webtab4_URL));
    tab5Args.putString("tabURL", getString(R.string.webtab5_URL));
    tab6Args.putString("tabURL", getString(R.string.webtab6_URL));
    tab7Args.putString("tabURL", getString(R.string.webtab7_URL));

    mTabsAdapter = new TabsAdapter(this, mViewPager);
    mTabsAdapter.addTab(bar.newTab().setText(getString(R.string.webtab1_name)),
            WebTabFragment.MyWebviewFragment.class, tab1Args);
    mTabsAdapter.addTab(bar.newTab().setText(getString(R.string.webtab2_name)),
            WebTabFragment.MyWebviewFragment.class, tab2Args);
    mTabsAdapter.addTab(bar.newTab().setText(getString(R.string.webtab3_name)),
            WebTabFragment.MyWebviewFragment.class, tab3Args);
    mTabsAdapter.addTab(bar.newTab().setText(getString(R.string.webtab4_name)),
            WebTabFragment.MyWebviewFragment.class, tab4Args);
    mTabsAdapter.addTab(bar.newTab().setText(getString(R.string.webtab5_name)),
            WebTabFragment.MyWebviewFragment.class, tab5Args);
    mTabsAdapter.addTab(bar.newTab().setText(getString(R.string.webtab6_name)),
            WebTabFragment.MyWebviewFragment.class, tab6Args);
    mTabsAdapter.addTab(bar.newTab().setText(getString(R.string.webtab7_name)),
            WebTabFragment.MyWebviewFragment.class, tab7Args);

    if (savedInstanceState != null) {
        bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
    }
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
}

/**
 * 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.
 */
public static class TabsAdapter extends FragmentPagerAdapter implements ActionBar.TabListener, ViewPager.OnPageChangeListener{
    private final Context mContext;
    //private final TabHost mTabHost;
    private final ActionBar mActionBar;
    private final ViewPager mViewPager;
    private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();

    static final class TabInfo {
        private final Class<?> clss;
        private final Bundle args;

        TabInfo(Class<?> _class, Bundle _args) {
            clss = _class;
            args = _args;
        }
    }

    public TabsAdapter(FragmentActivity activity, ViewPager pager) {
        super(activity.getSupportFragmentManager());
        mContext = activity;
        mActionBar = ((SherlockFragmentActivity) activity).getSupportActionBar();
        mViewPager = pager;
        mViewPager.setAdapter(this);
        mViewPager.setOnPageChangeListener(this);
  }

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

    @Override
    public int getCount()
    {
        int iCount = mTabs.size();
        return iCount;
    }

    @Override
    public Fragment getItem(int position)
    {
        TabInfo info = mTabs.get(position);
        return Fragment.instantiate(mContext, info.clss.getName(), info.args);
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
    {
    }

    @Override
    public void onPageSelected(int position)
    {
        mActionBar.setSelectedNavigationItem(position);
    }

    @Override
    public void onPageScrollStateChanged(int state)
    {
    }


    @Override
    public void onTabSelected(Tab tab)
    {
        Object tag = tab.getTag();
        for (int i=0; i<mTabs.size(); i++) {
            if (mTabs.get(i) == tag) {
                mViewPager.setCurrentItem(i);
            }
        }
    }

    @Override
    public void onTabUnselected(Tab tab)
    {
    }

    @Override
    public void onTabReselected(Tab tab)
    {
    }

}
}

Here's the code for the tab fragments: EDITED:

public class WebTabFragment extends SherlockFragmentActivity {

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

    if(savedInstanceState == null)
    {
       FragmentManager fm = getSupportFragmentManager();
        if (fm.findFragmentById(android.R.id.content) == null) {
            MyWebviewFragment myWebView = new MyWebviewFragment();
            fm.beginTransaction().add(android.R.id.content, myWebView).commit();
        }
    }
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    //outState.putString("tabNumber", mTabNumber);
}


public static class MyWebviewFragment extends SherlockFragment {
    final static private String tag = MyWebviewFragment.class.getSimpleName();
    String mTabURL;
    private WebView mWebView = null;
    static final int REFRESH_ID = Menu.FIRST;
    private ProgressDialog spinnerDlg;

    @Override
    public void onSaveInstanceState(Bundle outState)
    {
        if(mWebView.saveState(outState) == null)
            Log.i(tag,"Saving state FAILED!");
        else
            Log.i(tag, "Saving state succeeded.");
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState)
    {
        super.onActivityCreated(savedInstanceState);
        setHasOptionsMenu(true);
    }

    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        menu.add(Menu.NONE, REFRESH_ID, 0, getString(R.string.refresh_string))
        .setIcon(R.drawable.ic_action_refresh)
        .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);

    }

    @Override public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case REFRESH_ID:
                if(mWebView != null)
                    mWebView.reload();
                return true;

            default:
                return super.onOptionsItemSelected(item);
        }
    }

    /**
     * When creating, retrieve this instance's number from its arguments.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Tell the framework to try to keep this fragment around
        // during a configuration change.
        setRetainInstance(true);

        mTabURL = getArguments() != null ? getArguments().getString("tabURL") : "http://www.google.com";
    }

    /**
     * The Fragment's UI is just a simple text view showing its
     * instance number.
     */
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        // Create view object to return
        View v = inflater.inflate(R.layout.webview_layout, container, false);

        // Set up webview object
        if (mWebView != null) {
            mWebView.destroy();
        }
        mWebView = (WebView)v.findViewById(R.id.webview_fragment);
        mWebView.getSettings().setJavaScriptEnabled(true);
        mWebView.setOnKeyListener(new OnKeyListener(){
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event)
            {
                if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {
                    mWebView.goBack();
                    return true;
                }
                return false;
            }

        });

        // Check to see if it has been saved and restore it if true
        if(savedInstanceState != null)
        {
            if (savedInstanceState.isEmpty())
                Log.i(tag, "Can't restore state because bundle is empty.");
            else
            {
                if (mWebView.restoreState(savedInstanceState) == null)
                    Log.i(tag, "Restoring state FAILED!");
                else
                    Log.i(tag, "Restoring state succeeded.");
            }

        }
        else
        {
            // Load web page
            mWebView.setWebViewClient(new MyWebViewClient());
            mWebView.getSettings().setPluginsEnabled(true);
            mWebView.getSettings().setBuiltInZoomControls(false);
            mWebView.getSettings().setSupportZoom(false);
            mWebView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
            mWebView.getSettings().setAllowFileAccess(true);
            mWebView.getSettings().setDomStorageEnabled(true);
            mWebView.loadUrl(mTabURL);

        }
        return v;
    }

    @Override
    public void onDestroy()
    {
        super.onDestroy();
    }

    @Override
    public void onDestroyView()
    {
        super.onDestroyView();
    }

    @Override
    public void onPause()
    {
        super.onPause();
    }

    @Override
    public void onResume()
    {
        super.onResume();
    }


    @Override
    public void onConfigurationChanged(Configuration newConfig)
    {
        // TODO Auto-generated method stub
        super.onConfigurationChanged(newConfig);
    }


    public class MyWebViewClient extends WebViewClient {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
        // YouTube video link
            if (url.startsWith("http://youtu.be"))
            {
                String urlSubString = url.substring("http://youtu.be/".length());
                String newURL = String.format("http://www.youtube.com/v/%s", urlSubString);
                startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(newURL)));
                return (true);
            }

            return (false);
        }

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            super.onPageStarted(view, url, favicon);

            if(spinnerDlg == null)
            {
                spinnerDlg = new ProgressDialog(getActivity());
                spinnerDlg.setMessage("Loading....");
                spinnerDlg.show();
            }
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);

            if(spinnerDlg != null)
            {
                spinnerDlg.dismiss();
            }
            spinnerDlg = null;
       }
    }
}
}
JustLearningAgain
  • 2,227
  • 4
  • 34
  • 50

4 Answers4

4

Tabs count will start form 0 so in viewPager you have to set the screen limit like below

Example if you have 3 tabs just give

viewPager.setOffscreenPageLimit(2);

John
  • 1,407
  • 7
  • 26
  • 51
1

Thats one of the properties of tabs. It loads the second one so you aren't swiping to a null tab.. You're welcome to override the tab class and handle that yourself if you want :D.

ksudu94
  • 120
  • 11
0

If you are using fragments, consider using ViewPager. Its much easier to implement and faster in response.

example here, http://android-developers.blogspot.com/2011/08/horizontal-view-swiping-with-viewpager.html

dcanh121
  • 4,665
  • 11
  • 37
  • 84
  • 1
    Actually, I am using a Viewpager. – JustLearningAgain Mar 08 '12 at 01:07
  • Then I guess you are not using the TabHost and only using the ViewPager. Can you tell me what exactly is the problem you are facing. – dcanh121 Mar 08 '12 at 01:17
  • I think you might have identified my problem. I think I'm using a combination of both. That would explain the double call. I'll try to rewrite using just the ViewPager and let you know how it goes. – JustLearningAgain Mar 08 '12 at 03:36
  • I have cleaned up the code and put in the new code above (EDITED) and still I'm seeing the same problem. Interesting thing: Like I said, it's loading the next fragment, not just the current fragment. Here's something I've found and don't know if it's relevant. SEQUENCE: Load tab1-> loads tab1 & tab2 --- Click on tab2-> tab2 is brought to the front (because it was already loaded) & tab3 is loaded--- Click on tab1-> tab1 is brought to the front & then onPause and onDestroyView is called for tab3. Weird??? – JustLearningAgain Mar 08 '12 at 15:28
  • I think it works that way. Do you not want to destroy the tab3 if tab2 is selected? I can give you some code. – dcanh121 Mar 08 '12 at 17:35
  • Weird if it does. I don't want any of the tabs to be destroyed. They are in fact the whole app and I'd prefer that they only be queried once. The other thing is that when I go back to a tab, it requeries another tab. It kind of defeats the whole purpose of not redrawing the page. – JustLearningAgain Mar 08 '12 at 19:57
  • I had the same problem. My requirement was to load the tab only once. If required, user can reload using the menu. I will look at my code and give you the snippet. – dcanh121 Mar 08 '12 at 23:51
  • Awesome. I have added a refresh button to the action bar for exactly the same reason. I can't wait to see your implementation. This drawing the tab I'm on as well as the adjacent is killing me. There's no way it is supposed to work like that. – JustLearningAgain Mar 09 '12 at 03:53
  • Its actually simple code which can prevent from destroying the fragments when switching between the tabs. In the class which extends FragmentPagerAdapter, add empty destroyItem() method. @Override public void destroyItem(View container, int position, Object object){ } – dcanh121 Mar 09 '12 at 04:04
0

You say

onCreate is called as well (should not happen)

But that's exactly what should happen if you use ViewPager. How else would you see the fragment if you swipe it half way between the first and second tab?

If you tap on tab 2, it will be recreated maybe, because it is destroyed. Try to override getItemPosition like this:

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }
Dante
  • 1,104
  • 1
  • 10
  • 15
  • That actually makes sense. I have a spinner so that the user knows that the page is loading. I need to make sure that the spinner only appears if the current tab is querying the web. Could I use getItemPosition to do this? – JustLearningAgain Mar 09 '12 at 03:59
  • 1
    Actually, POSITION_NONE will call onDestroy for that fragment. So it should be another value. I'll come back to it how I solved it, but you can also check this link http://stackoverflow.com/questions/7263291/viewpager-pageradapter-not-updating-the-view – Dante Mar 09 '12 at 09:17