How to restore the state of a WebView in a layout “Tabs + Swipe” with ViewPager and FragmentPagerAdapter

In the post Tabs and swipe views Szymon asks how to implement a WebView in order to preserve its state moving from one tab to another in a similar way as explained in How to save the state of a WebView inside a Fragment of an Action Bar.

Starting from the code in Tabs and swipe views

  1. add a permission to access to internet in AndroidManifest.xml
    <uses-permission android:name="android.permission.INTERNET"/>
    
  2. replace the file res/layout/tab3.xml
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/tab3"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical" >
    
        <WebView
            android:id="@+id/webView1"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </LinearLayout>
    

    where I add a WebView in the third tab;
    as you’ll verify, this WebView still retains its state switching from an adjacent tab (from the second tab in this example) without the need of the code to restore the state of the WebView, instead the WebView is reset switching from a not adjacent tab (from the first tab in this example);

  3. replace the file eu/lucazanini/viewpager/MainActivity.java
    package eu.lucazanini.swipeviews;
    
    /**
     * Copyright 2012 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    import android.app.ActionBar;
    import android.app.FragmentTransaction;
    import android.os.Bundle;
    import android.support.v4.app.Fragment;
    import android.support.v4.app.FragmentActivity;
    import android.support.v4.app.FragmentManager;
    import android.support.v4.app.FragmentPagerAdapter;
    import android.support.v4.view.ViewPager;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.webkit.WebView;
    import android.webkit.WebViewClient;
    
    public class MainActivity extends FragmentActivity implements
    	ActionBar.TabListener {
    
        /**
         * The {@link android.support.v4.view.PagerAdapter} that will provide
         * fragments for each of the three primary sections of the app. We use a
         * {@link android.support.v4.app.FragmentPagerAdapter} derivative, which
         * will keep every loaded fragment in memory. If this becomes too memory
         * intensive, it may be best to switch to a
         * {@link android.support.v4.app.FragmentStatePagerAdapter}.
         */
        CollectionPagerAdapter mCollectionPagerAdapter;
    
        /**
         * The {@link android.support.v4.view.ViewPager} that will display the
         * object collection.
         */
        ViewPager mViewPager;
    
        public void onCreate(Bundle savedInstanceState) {
    	super.onCreate(savedInstanceState);
    	setContentView(R.layout.main);
    
    	// Create an adapter that when requested, will return a fragment
    	// representing an object in
    	// the collection.
    	//
    	// ViewPager and its adapters use support library fragments, so we must
    	// use
    	// getSupportFragmentManager.
    	mCollectionPagerAdapter = new CollectionPagerAdapter(
    		getSupportFragmentManager());
    
    	// Set up action bar.
    	final ActionBar actionBar = getActionBar();
    
    	// Specify that the Home/Up button should not be enabled, since there is
    	// no hierarchical
    	// parent.
    	actionBar.setHomeButtonEnabled(false);
    
    	// Specify that we will be displaying tabs in the action bar.
    	actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    
    	// Set up the ViewPager, attaching the adapter and setting up a listener
    	// for when the
    	// user swipes between sections.
    	mViewPager = (ViewPager) findViewById(R.id.pager);
    	mViewPager.setAdapter(mCollectionPagerAdapter);
    	mViewPager
    		.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
    		    @Override
    		    public void onPageSelected(int position) {
    			// When swiping between different app sections, select
    			// the corresponding tab.
    			// We can also use ActionBar.Tab#select() to do this if
    			// we have a reference to the
    			// Tab.
    			actionBar.setSelectedNavigationItem(position);
    		    }
    		});
    
    	// For each of the sections in the app, add a tab to the action bar.
    	for (int i = 0; i < mCollectionPagerAdapter.getCount(); i++) {
    	    // Create a tab with text corresponding to the page title defined by
    	    // the adapter.
    	    // Also specify this Activity object, which implements the
    	    // TabListener interface, as the
    	    // listener for when this tab is selected.
    	    actionBar.addTab(actionBar.newTab()
    		    .setText(mCollectionPagerAdapter.getPageTitle(i))
    		    .setTabListener(this));
    	}
        }
    
        public void onTabUnselected(ActionBar.Tab tab,
    	    FragmentTransaction fragmentTransaction) {
        }
    
        public void onTabSelected(ActionBar.Tab tab,
    	    FragmentTransaction fragmentTransaction) {
    	// When the given tab is selected, switch to the corresponding page in
    	// the ViewPager.
    	mViewPager.setCurrentItem(tab.getPosition());
        }
    
        public void onTabReselected(ActionBar.Tab tab,
    	    FragmentTransaction fragmentTransaction) {
        }
    
        /**
         * A {@link FragmentPagerAdapter} that returns a fragment corresponding to
         * one of the primary sections of the app.
         */
        public class CollectionPagerAdapter extends FragmentPagerAdapter {
    
    	final int NUM_ITEMS = 3; // number of tabs
    
    	public CollectionPagerAdapter(FragmentManager fm) {
    	    super(fm);
    	}
    
    	@Override
    	public Fragment getItem(int i) {
    	    Fragment fragment = new TabFragment();
    	    Bundle args = new Bundle();
    	    args.putInt(TabFragment.ARG_OBJECT, i);
    	    fragment.setArguments(args);
    	    return fragment;
    	}
    
    	@Override
    	public int getCount() {
    	    return NUM_ITEMS;
    	}
    
    	@Override
    	public CharSequence getPageTitle(int position) {
    	    String tabLabel = null;
    	    switch (position) {
    	    case 0:
    		tabLabel = getString(R.string.label1);
    		break;
    	    case 1:
    		tabLabel = getString(R.string.label2);
    		break;
    	    case 2:
    		tabLabel = getString(R.string.label3);
    		break;
    	    }
    
    	    return tabLabel;
    	}
        }
    
        /**
         * A fragment that launches other parts of the demo application.
         */
        public static class TabFragment extends Fragment {
    
    	public static final String ARG_OBJECT = "object";
    
    	private WebView webView;
    	private Bundle webViewBundle;
    
    	@Override
    	public void onCreate(Bundle savedInstanceState) {
    	    super.onCreate(savedInstanceState);
    
    	    webViewBundle = new Bundle();
    	}
    
    	@Override
    	public View onCreateView(LayoutInflater inflater, ViewGroup container,
    		Bundle savedInstanceState) {
    
    	    Bundle args = getArguments();
    	    int position = args.getInt(ARG_OBJECT);
    
    	    int tabLayout = 0;
    	    switch (position) {
    	    case 0:
    		tabLayout = R.layout.tab1;
    		break;
    	    case 1:
    		tabLayout = R.layout.tab2;
    		break;
    	    case 2:
    		tabLayout = R.layout.tab3;
    		break;
    	    }
    
    	    View rootView = inflater.inflate(tabLayout, container, false);
    
    	    webView = (WebView) rootView.findViewById(R.id.webView1);
    	    if (webView != null) {
    		webView.setWebViewClient(new WebViewClient());
    
    		if (webViewBundle == null || webViewBundle.isEmpty()) {
    		    webView.loadUrl("http://www.lucazanini.eu");
    		} else {
    		    webView.restoreState(webViewBundle);
    		    webViewBundle.clear();
    		}
    	    }
    
    	    return rootView;
    	}
    
    	@Override
    	public void onPause() {
    	    super.onPause();
    
    	    if (webView != null && webViewBundle.isEmpty()) {
    		webView.saveState(webViewBundle);
    	    }
    	}
        }
    
    }
    

    in the internal static class TabFragment:

    • in the onCreate method I initialize the Bundle webViewBundle where I save the state of the WebView
    • in the onCreateView method starting from line 207 I intialize WebView and if webViewBundle is not null I restore the WebView
    • in the onPause method I save the state of the WebView in the Bundle webViewBundle

    if you comment the lines 211 and 213-216 you can verify what I wrote before and i.e. the fragment is not destroyed switching from a tab to another adjacent tab and then also the state of the WebView is preserved:
    this is the default behavior of the ViewPager that retains the selected tab and the two adjacent;
    the code in the lines 211-216 and in onPause method is used to restore the state of the WebView if you select the third tab from the first tab.

Switching to the third tab from the first one, the state of the WebView is restored but the WebView is also reloaded unlike what happens switching from the second tab to the third tab when the entire fragment is stored in memory (you can also see this from the different loading times of the third tab depending on whether you are coming from the first or second tab).
You can change the number of the stored tabs from ViewPager using the setOffscreenPageLimit method whose argument is the number of pages (tabs) before and after the current page to keep in memory.

Consider the following code for eu/lucazanini/viewpager/MainActivity.java

package eu.lucazanini.swipeviews;

/**
 * Copyright 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import android.app.ActionBar;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.webkit.WebViewClient;

public class MainActivity extends FragmentActivity implements
	ActionBar.TabListener {

    /**
     * The {@link android.support.v4.view.PagerAdapter} that will provide
     * fragments for each of the three primary sections of the app. We use a
     * {@link android.support.v4.app.FragmentPagerAdapter} derivative, which
     * will keep every loaded fragment in memory. If this becomes too memory
     * intensive, it may be best to switch to a
     * {@link android.support.v4.app.FragmentStatePagerAdapter}.
     */
    CollectionPagerAdapter mCollectionPagerAdapter;

    /**
     * The {@link android.support.v4.view.ViewPager} that will display the
     * object collection.
     */
    ViewPager mViewPager;

    public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main);

	// Create an adapter that when requested, will return a fragment
	// representing an object in
	// the collection.
	//
	// ViewPager and its adapters use support library fragments, so we must
	// use
	// getSupportFragmentManager.
	mCollectionPagerAdapter = new CollectionPagerAdapter(
		getSupportFragmentManager());

	// Set up action bar.
	final ActionBar actionBar = getActionBar();

	// Specify that the Home/Up button should not be enabled, since there is
	// no hierarchical
	// parent.
	actionBar.setHomeButtonEnabled(false);

	// Specify that we will be displaying tabs in the action bar.
	actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

	// Set up the ViewPager, attaching the adapter and setting up a listener
	// for when the
	// user swipes between sections.
	mViewPager = (ViewPager) findViewById(R.id.pager);
	mViewPager.setOffscreenPageLimit(2);
	mViewPager.setAdapter(mCollectionPagerAdapter);
	mViewPager
		.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
		    @Override
		    public void onPageSelected(int position) {
			// When swiping between different app sections, select
			// the corresponding tab.
			// We can also use ActionBar.Tab#select() to do this if
			// we have a reference to the
			// Tab.
			actionBar.setSelectedNavigationItem(position);
		    }
		});

	// For each of the sections in the app, add a tab to the action bar.
	for (int i = 0; i < mCollectionPagerAdapter.getCount(); i++) {
	    // Create a tab with text corresponding to the page title defined by
	    // the adapter.
	    // Also specify this Activity object, which implements the
	    // TabListener interface, as the
	    // listener for when this tab is selected.
	    actionBar.addTab(actionBar.newTab()
		    .setText(mCollectionPagerAdapter.getPageTitle(i))
		    .setTabListener(this));
	}
    }

    public void onTabUnselected(ActionBar.Tab tab,
	    FragmentTransaction fragmentTransaction) {
    }

    public void onTabSelected(ActionBar.Tab tab,
	    FragmentTransaction fragmentTransaction) {
	// When the given tab is selected, switch to the corresponding page in
	// the ViewPager.
	mViewPager.setCurrentItem(tab.getPosition());
    }

    public void onTabReselected(ActionBar.Tab tab,
	    FragmentTransaction fragmentTransaction) {
    }

    /**
     * A {@link FragmentPagerAdapter} that returns a fragment corresponding to
     * one of the primary sections of the app.
     */
    public class CollectionPagerAdapter extends FragmentPagerAdapter {

	final int NUM_ITEMS = 3; // number of tabs

	public CollectionPagerAdapter(FragmentManager fm) {
	    super(fm);
	}

	@Override
	public Fragment getItem(int i) {
	    Fragment fragment = new TabFragment();
	    Bundle args = new Bundle();
	    args.putInt(TabFragment.ARG_OBJECT, i);
	    fragment.setArguments(args);
	    return fragment;
	}

	@Override
	public int getCount() {
	    return NUM_ITEMS;
	}

	@Override
	public CharSequence getPageTitle(int position) {
	    String tabLabel = null;
	    switch (position) {
	    case 0:
		tabLabel = getString(R.string.label1);
		break;
	    case 1:
		tabLabel = getString(R.string.label2);
		break;
	    case 2:
		tabLabel = getString(R.string.label3);
		break;
	    }

	    return tabLabel;
	}
    }

    /**
     * A fragment that launches other parts of the demo application.
     */
    public static class TabFragment extends Fragment {

	public static final String ARG_OBJECT = "object";

	private WebView webView;
	
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
		Bundle savedInstanceState) {

	    Bundle args = getArguments();
	    int position = args.getInt(ARG_OBJECT);

	    int tabLayout = 0;
	    switch (position) {
	    case 0:
		tabLayout = R.layout.tab1;
		break;
	    case 1:
		tabLayout = R.layout.tab2;
		break;
	    case 2:
		tabLayout = R.layout.tab3;
		break;
	    }

	    View rootView = inflater.inflate(tabLayout, container, false);

	    webView = (WebView) rootView.findViewById(R.id.webView1);
	    if (webView != null) {
		webView.setWebViewClient(new WebViewClient());
		webView.loadUrl("http://www.lucazanini.eu");
	    }

	    return rootView;
	}
    }

}

The key point is the line 81 that sets to 2 the number of pages to keep in memory before and after the current one, and in the case of three tabs as in this example it means that all the pages are in memory.
Now the state of WebView is preserved without the need to use a Bundle to save and restore the state because all fragments are stored in memory.

References:
ViewPager


Comments

20 responses to “How to restore the state of a WebView in a layout “Tabs + Swipe” with ViewPager and FragmentPagerAdapter”

  1. Maruf Ahmed Avatar
    Maruf Ahmed

    Sir,
    I am developing an app. In my app I have bottom navigation bar. In the bottom navigation bar I have only one button called Home which is inside HomeFragment. In my homefragment I have 4 images. Clicking each image takes to different fragments .Clicking 1st image takes to MenFragment and inside MenFragment there are 4 images. Now clicking these images take to another fragment which is webviewfragment. Till this part everything is perfect.
    Now clicking on the 2nd image in the homefragment takes to annother fragment called womenfragment containing 3 tablayouts fragments. Here each tab contains 4 images. Now I want to move from tablayouts to webview fragment clicking on the images inside different fragment in tablayout. here I have used same codes that i have used to move from menfragment to webview fragment.But when i am clicking on the images in tablayout,it is killing my app and taking me out of my app.Is there any way I can do the second part.

  2. Hi Luca,
    One question: Is this compatible with android versions older than 3.0 ?
    Following your tutorials it’s working great on android 4.1 and 4.2 but when i tried it on android 2.3.6 i’m getting:
    java.lang.ClassNotFoundException in dalvik.system.PathClassLoader.findClass
    that exception is thrown at the moment I execute the app.

    I tried installing latest compatibility package because I tought actionbar was not supported. I changed my mainclass from FragmentActivity to ActionBarActivity and set theme to compattheme and it’s working on 4.1 and 4.2 but in android 2.3.6 i’m having the same problem again.
    Any suggestions or workarounds?
    Thanks !

    1. No, sorry, it doesn’t work for versions older than 3.0.
      For example getActionBar() at line 67 requires API level 11 or Android 3.0

  3. Hi, first i want to say thanks, this is working GREAT.
    The comments in code make it completely clear.
    But i have one issue and maybe you can help me :
    I managed to put 3 webviews in 3 fragments but whenever i rotate the screen , the webviews gets reloaded.
    This is usually solved by overriding on configurationchange and saving the state of the webview but I cannot do that with 3 webviews at the same time, or at least i dont know how. Do you have any idea on how to do that?
    Thanks for your time

    1. If there aren’t changes in layout on in other configurations when you rotate the screen you can try this line in AndroidManifest.xml:
      android:configChanges=”orientation|screenSize”

      See here for more info.

      1. Oohh, i read that article before and tried out but it didn’t work, now i realize I made a a mistake when I wrote the manifest.
        It’s working great, can’t believe it was so easy haha.
        Thanks a lot from Argentina! keep this site up, it’s great !

  4. Hello Sir , i would like to ad a webview on each tab , and load 3 different url’s for each tab

    1. You can modify onCreateView in MainActivity.java to handle 3 webviews

      1. Same question here?
        all tabs loads the same webpage.. how to change this code so each can have a diferent webpage

        /**
        * A fragment that launches other parts of the demo application.
        */
        public static class TabFragment extends Fragment {

        public static final String ARG_OBJECT = “object”;

        private WebView webView;

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

        Bundle args = getArguments();
        int position = args.getInt(ARG_OBJECT);

        int tabLayout = 0;
        switch (position) {
        case 0:
        tabLayout = R.layout.auto;
        break;
        case 1:
        tabLayout = R.layout.fragment_main;
        break;
        case 2:
        tabLayout = R.layout.fragment_main;
        break;
        }

        View rootView = inflater.inflate(tabLayout, container, false);

        webView = (WebView) rootView.findViewById(R.id.browser);
        if (webView != null) {
        webView.setWebViewClient(new WebViewClient());
        webView.loadUrl(“http://www.headgone.net”);
        }

        return rootView;

        }
        }

        1. figured it out…

  5. Hello, thank you for the help but I have a question

    When I open a webpage and click on some link it will open the link but when I want to go back to the first page by clicking the back button, it directly exits the program.
    Need help fast!!!

    Can you write a new article on going back to the primary fragment. Thanks.

    1. You can add this code to MainActivity:

          private boolean webViewDisplayed = false;
      
          @Override
          public boolean onKeyDown(int keyCode, KeyEvent event) {
      	if (event.getAction() == KeyEvent.ACTION_DOWN) {
      	    switch (keyCode) {
      	    case KeyEvent.KEYCODE_BACK:
      		WebView webView = (WebView) findViewById(R.id.webView1);
      		if (webViewDisplayed && webView != null && webView.canGoBack()) {
      		    Log.d("TEST", "BACK");
      		    webView.goBack();
      		} else {
      		    Log.d("TEST", "FINISH");
      		    finish();
      		}
      		return true;
      	    }
      
      	}
      	return super.onKeyDown(keyCode, event);
          }
      

      and replace onTabUnselected and onTabSelected methods:

          public void onTabUnselected(ActionBar.Tab tab,
      	    FragmentTransaction fragmentTransaction) {
      	webViewDisplayed = false;
          }
      
          public void onTabSelected(ActionBar.Tab tab,
      	    FragmentTransaction fragmentTransaction) {
      	mViewPager.setCurrentItem(tab.getPosition());
      	if (tab.getPosition() == 2)
      	    webViewDisplayed = true;
          }
      
      1. Sir, Is it a much to ask if I asked you to make a post on displaying three different web pages on three different tabs. I tired the thing you said it works but when I try to replace tab1 and tab2 with tab3 then it doesn’t work. the app exits after I press back button before going to the parent fragment.

        Let me put it clear.
        I want 5 tabs with different web pages. and in every web pages after i click a link I should be able to go back to the previous fragment.
        I would be so much thankful if you could help.

        1. What should the back button do?
          Is it like the back button in the browser, i.e. open the previous page in the web view?
          If so the code in my last comment does this, but it works only for the third tab because of the code in onTabSelected, but you can extend to the other tabs.
          But maybe you want the back button going to a specific fragment or tab.
          If so you can try:

              @Override
              public boolean onKeyDown(int keyCode, KeyEvent event) {
          	if (event.getAction() == KeyEvent.ACTION_DOWN) {
          	    switch (keyCode) {
          	    case KeyEvent.KEYCODE_BACK:
          		mViewPager.setCurrentItem(0); // 0 for first tab, 1 for second
          					      // one and so on
          		return true;
          	    }
          
          	}
          	return super.onKeyDown(keyCode, event);
              }
          
          1. Yes, I want it like back button in the browser. but with this code now when ever I do back it goes to tab 1. I mean when I am in tab 1 and open a link than when i do back it should go to previous link after that may be exit the software.

          2. The code in my first comment does what you ask for the third tab, write the code for the other webviews in others tabs.

      2. Hello, first of all thank you for the fantastic tutorials you have on the web.
        I would like to ask you a question, how can I make BACK button works alike in all WebView? If I explain the code as above only works with WebView1 the rest exits the application.

        Thank you very much.

  6. Ethan Thomas Avatar
    Ethan Thomas

    Hi! Thank you so much! This is just what I needed! If you have time can you do a scrollable tabs + swipe version of this?

    Thank you!

  7. Thank you very much for long and helpful post. Working great.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.