Android development blog
Android tutorial about dev topics

Fragment in Android: Tutorial with Example using WebView

by Francesco Azzola, March 21, 2013
In this post we want to explain how to use fragment in Android with a real example.
In the last post we talked about Fragment in Android and how we can use it to support multiple screen. We described their lifecycle and how we can use it. In this post we want to go deeper and create an example that helps us to understand better how we can use fragments.
As example we want to create a simple application that is built by two fragments:

  • one that shows a list of links and
  • another one that show the web page in a WebView.
We can suppose we have two different layouts one for portrait mode and one for landscape. In the landscape mode we want something like the image below:

fragment_webview_landscape

while, in the portrait mode we want something like:

fragment_webview_portrait


CREATE LAYOUT

The first step we have to do is creating our layout. As we said we need two different layout one for portrait and one for landscape. So we have to create two xml file under res/layout (for portrait mode) and one under res/layout-land (for landscape mode). Of course we could customize more our layout including other specs but it is enough for now. These two files are called activity_layout.xml.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <fragment android:id="@+id/listFragment"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              class="com.survivingwithandroid.fragment.LinkListFragment"/>
       
</RelativeLayout>

This one is for the portrait mode and as we notice we have only one fragment containing just the link list. We will see later how to create this fragment. We need, moreover, another layout as it is clear from the pic above, the one that contains the WebView.

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical" >

 
    <WebView android:id="@+id/webPage"

             android:layout_height="wrap_content"

             android:layout_width="wrap_content"/>

</LinearLayout>


For the landscape mode we have something very similar plus the FrameLayout component in the same layout.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity"
    android:orientation="horizontal" >

    <fragment android:id="@+id/listFragment"
              android:layout_width="0dp"
              android:layout_height="wrap_content"
              class="com.survivingwithandroid.fragment.LinkListFragment"
              android:layout_weight="2"/>
    
    <FrameLayout android:id="@+id/fragPage" 
                 android:layout_width="0dp"
                 android:layout_height="match_parent"
                 android:layout_weight="4"
                 />

</LinearLayout>



CREATE LINK LIST FRAGMENT

As you have already noticed both layout have a common fragment that is called LinkListFragment. We have to create it. If you didn’t read already the post explaining the lifecycle it is time you give a look. In this case we don’t have to override all the methods in the fragment lifecycle, but those important to control its behaviour. In our case we need to override:

  • onCreateView
  • onAttach

In this fragment we use a simple ListView to show the links and a simple adapter to customize the way how the items are shown. We don’t want to spend much time on how to create a custom adapter because it is out of this topic you can refer here to have more information. Just to remember in the onCreateView fragment method we simply inflate the layout and initialize the custom adapter. As XML layout to inflate in the fragment we have:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
    <ListView android:id="@+id/urls" 
              android:layout_height="match_parent"
              android:layout_width="wrap_content"/>

</LinearLayout>

while in the method looks like:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    Log.d("SwA", "LV onCreateView");
    View v = inflater.inflate(R.layout.linklist_layout, container, false);
    ListView lv = (ListView) v.findViewById(R.id.urls);
    la = new LinkAdapter(linkDataList, getActivity());
    lv.setAdapter(la);
    lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
         public void onItemClick(AdapterView<?> parentAdapter, View view, int position,
                 long id) {
            LinkData data = ( (LinkAdapter) la ).getItem(position);
            ( (ChangeLinkListener)  getActivity()).onLinkChange(data.getLink());
        }

    });
    return v;
}

In this method we simply set our custom adapter and the set the listener when the user clicks on an item. We will cover this later (if you are curious see Inter fragment communication).

In the onAttach method we verify that the activity that holds the fragment implements a specific interface.

@Override
    public void onAttach(Activity activity) {
        // We verify that our activity implements the listener
        if (! (activity instanceof ChangeLinkListener) )
            throw new ClassCastException();
        
        
        super.onAttach(activity);
    }

We will clarify why we need this control later.


FRAGMENT COMMUNICATION

Basically in our example we have two fragments and they need to exchange information so that when user select an item in the fragment 1 (LinkListFragment) the other one (WebViewFragment) shows the web page corresponding to the link. So we need to find a way to let these fragments to exchange data.
On the other way we know that a fragment is a piece of code that can be re-used inside other activity so we don’t want to bind our fragment to a specific activity to not invalidate our work. In Java if we want to decouple two classes we can use an interface. So this interface solution fits perfectly. On the other hand we don’t want that our fragment exchange information directly because each fragment can rely only on the activity that holds it. So the simplest solution is that the activity implements an interface.



inter_fragment_communication



So in our case we define an interface called ChangeLinkListener that has only one method:

public interface ChangeLinkListener {

    public void onLinkChange(String link);
}

We have, more over, to verify that our activity implements this interface to be sure we can call it. The best place to verify it is in the onAttach method (see above) and at the end we need to call this method when the user selects an item in the ListView:

lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
     public void onItemClick(AdapterView<?> parentAdapter, View view, int position,
             long id) {
        LinkData data = ( (LinkAdapter) la ).getItem(position);
        (ChangeLinkListener)  getActivity()).onLinkChange(data.getLink());
    }

});


PROGRAMMING MAIN ACTIVITY: FIND FRAGMENT

By now we talked about fragments only, but we know that fragments exists inside a “father” activity that control them. So we have to create this activity but we have to do much more.
As we said before this activity has to implements a custom interface so that it can receive data from the LinkListFragment. In this method (onLinkChange) we have somehow to control if we are in landscape mode or in portrait mode, because in the first case we need to update the WebViewFragment while in the second case we have to start another activity. How can we do it? The difference in the layout is the presence of the FrameLayout. If it is present it means we are in landscape mode otherwise in portrait mode. So the code in the onLinkChange method is:

@Override
public void onLinkChange(String link) {
    System.out.println("Listener");
    // Here we detect if there's dual fragment
    if (findViewById(R.id.fragPage) != null) {        WebViewFragment wvf = (WebViewFragment) getFragmentManager().findFragmentById(R.id.fragPage);
       
        if (wvf == null) {
            System.out.println("Dual fragment - 1");
            wvf = new WebViewFragment();
            wvf.init(link);
            // We are in dual fragment (Tablet and so on)
            FragmentManager fm = getFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            
            
            //wvf.updateUrl(link);
            ft.replace(R.id.fragPage, wvf);
            ft.commit();
            
        }
        else {
         Log.d("SwA", "Dual Fragment update");
         wvf.updateUrl(link);
        }
    }
    else {
        System.out.println("Start Activity");
        Intent i = new Intent(this, WebViewActivity.class);
        i.putExtra("link", link);
        startActivity(i);
    }
    
}

Let’s analyse this method. The first part (line 5) verify that exists the FrameLayout. If it exists we use the FragmentManager to find the fragment relative to the WebViewFragment.  If this fragment is null (so it is the first time we use it) we simply create it and put this fragment “inside” the FrameLayout (line 7-20). If this fragment already exists we simply update the url (line 23). If we aren’t in landscape mode, we can start a new activity passing data as an Intent (line 28-30).

WEBVIEW FRAGMENT AND WEBVIEWACTIVITY: The web page


Finally we analyze the WebViewFragment. It is really simple, it is just override some Fragment method to customize its behaviour:

public class WebViewFragment extends Fragment {

    private String currentURL;
    
    public void init(String url) {
        currentURL = url;
    }
    
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {        
        super.onActivityCreated(savedInstanceState);
    }

    @Override
   public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        
        Log.d("SwA", "WVF onCreateView");
        View v = inflater.inflate(R.layout.web_layout, container, false);
        if (currentURL != null) {
            Log.d("SwA", "Current URL  1["+currentURL+"]");
            
            WebView wv = (WebView) v.findViewById(R.id.webPage);
            wv.getSettings().setJavaScriptEnabled(true);
            wv.setWebViewClient(new SwAWebClient());
            wv.loadUrl(currentURL);
                 
        }
        return v;
    }
    
   public void updateUrl(String url) {
        Log.d("SwA", "Update URL ["+url+"] - View ["+getView()+"]");
        currentURL = url;

        WebView wv = (WebView) getView().findViewById(R.id.webPage);
        wv.getSettings().setJavaScriptEnabled(true);
        wv.loadUrl(url);

    }
    
    private class SwAWebClient extends WebViewClient {

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            return false;
        }
        
    }
    
}

In the onCreateView method we simply inflate our layout inside the fragment and verify that the url to show is not null. If so we simply show the page (line 15-30). In the updateUrl we simply find the WebView component and update its url.

In the portrait mode we said we need to start another activity to show the webpage, so we need an activity (WebViewActivity). It is really simple and i just show the code without any other comment on it:
public class WebViewActivity extends FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {        
        super.onCreate(savedInstanceState);
        
        WebViewFragment wvf = new WebViewFragment();
        
        Intent i = this.getIntent();
        String link = i.getExtras().getString("link");
        
        Log.d("SwA", "URL ["+link+"]");
        wvf.init(link);
        getFragmentManager().beginTransaction().add(android.R.id.content, wvf).commit();
        
    }

    
}

Source code @ github.

12 comments:

  1. Nice post.Give it up. Thanks for share this article. For more visit:Web App Development

    ReplyDelete
  2. expandable listview layout how to set in tablet screen using fragment?
    pls help me ASAP. thank u in advance.

    ReplyDelete
  3. survivingwithandroidSeptember 6, 2013 at 2:23 PM

    If i get your question correctly, you want to replace ListView with ExpandableListView. In this case you have to chage in the layout the widget. If you want to know how to use ExpandableListView you can give a look here http://www.survivingwithandroid.com/2013/01/android-expandablelistview-baseexpandablelistadapter.html

    ReplyDelete
  4. Sources don't work, missing class name in fragments (in activity_main.xml).
    You have to specify which class the deflater has to instantiate.

    ReplyDelete
  5. survivingwithandroidSeptember 10, 2013 at 12:46 PM

    I will check the source code to know if i missed some classes. Thx for your support

    ReplyDelete
  6. I may be missing something, but where are LinkData, LinkAdapter, etc found? Because I can't find what class to import here

    ReplyDelete
  7. survivingwithandroidSeptember 16, 2013 at 9:19 PM

    There are in the source code. https://github.com/survivingwithandroid/Surviving-with-android/tree/master/FragmentTutorialWeb/src/com/survivingwithandroid/fragment/model for example. What r u missing?

    ReplyDelete
  8. Anyone having trouble getting the source to run- You need to add this line-

    class="com.survivingwithandroid.fragment.LinkListFragment"

    to the fragment element in each of the activity_main.xml files under res\layout and res\layout-land

    It is shown in the code listing above but not included in the source code download on github.

    Hope this helps.
    (Portrait mode works as expected but I am still not seeing two fragments in the landscape view)

    ReplyDelete
  9. Ok, landscape mode is not working in the github source because layout-land\activity_main.xml file contains an incorrect android:layout_width for fragment element.
    It should be- android:layout_width="0dp"

    Again the code block in the article shows the correct version but the github source is incorrect.

    ReplyDelete
  10. I tried to run this on Eclipse Juno with Android 4.4.2 SDK and got this error
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.survivingwithandroid.fragment/com.survivingwithandroid.fragment.MainActivity}: android.view.InflateException: Binary XML file line #12: Error inflating class fragment
    Caused by: android.view.InflateException: Binary XML file line #12: Error inflating class fragment

    Can the author please help?

    ReplyDelete
  11. I managed to resolve the problem by editing AndroidManifest.xml with this line

    Hope that author will update example for Fragment V4.

    ReplyDelete
  12. Sorry.. I was running another example. The problem is not fixed.. Please help!

    ReplyDelete

Related Posts with Thumbnails