Monday, February 9, 2015

Android WebView: Displaying web content in your app

The web has become a ubiquitous medium for information and communication. We use the web to disseminate information about ourselves, our companies, and our thoughts. We consume our news and other information about the world on the web. We purchase our groceries and gifts on the web. We even communicate with our friends, families, and loved ones over the web.

When we think about the web for native mobile applications we tend to think of the web as a data transfer mechanism. We talk about the cloud, JSON, XML, and RSS; all of which are important but often we need the web as more than the backbone of information. There are a couple of reasons you may want to display web content in your app instead of a native view:

  • You want to show external content without leaving your app.
  • You have dynamic content whose structure changes often.
  • You want to display legal pages (terms of service, end user license agreements, etc) directly from your site.

Today I'm going to start a series on the Android WebView. In this post we'll talk about the basic's of setting up a WebView to display web content in your app. In other posts I'll explain how to interact with web content via Javascript from your app as well as interacting with your app from Javascript. Finally I'll go over some gotchas or hurdles that it's important to be aware of when using a web view in your app.

Initializing the WebView


Initializing the WebView should be done when your Activity or Fragment is created. While the default settings the WebView uses will probably work for most basic web pages, you can be more explicit about how your WebView acts. You specify your preferences using the WebSettings object on your WebView.

Obtaining your WebView's settings is as simple as calling getSettings() on the WebView object. Here's an example of changing some of the default WebView settings:

private void initializeWebView(Bundle savedInstanceState)
{
    /** make sure you have a WebView in your layout with the id: browser **/
    WebView browser = (WebView)getView().findViewById(R.id.browser);

    // I like my scroll bars inside the content
    browser.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);

    // allow web pages to execute Javascript.
    browser.getSettings().setJavaScriptEnabled(true);

    // if you know your web pages use a different encoding than utf-8 you can change it.
    browser.getSettings().setDefaultTextEncodingName("utf-8");

    // Start with content zoomed all the way out.
    browser.getSettings().setLoadWithOverviewMode(true);

    // Allow zooming with the default zoom controls
    browser.getSettings().setSupportZoom(true);
    browser.getSettings().setBuiltInZoomControls(true);

    // Change how the web page is laid out.
    // See WebSettings.LayoutAlgorithm for more detail.
    browser.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);

    // Allow the HTML viewport meta tag to specify the width
    browser.getSettings().setUseWideViewPort(true);

    // Used to handle external interactions. More details below.
    browser.setWebChromeClient(this.getWebChromeClient());

    // Used to handle page events. More details below.
    browser.setWebViewClient(this.getWebViewClient());
}

Loading A Web Page


Once you have a WebView added to your app displaying web pages is as simple as calling loadUrl on the WebView. For example:

private void loadWebPage(String url)
{
    /** make sure you have a WebView in your layout with the id: browser **/
    WebView browser = (WebView)getView().findViewById(R.id.browser);
    browser.loadUrl(url);
}

Loading HTML Directly


Loading web data into your page doesn't have to be done using a remote URL. If you already have the HTML content you'd like to display (possibly from an RSS feed or offline content), you can do so very easily by first converting the content to Base64 and then creating a data URI to display the content. For example, you can generate a url for HTML content you already have that the WebView can display as follows:

private String getUrlFromHTML(String html)
{
    String base64Data = Base64.encodeToString(html.getBytes(), Base64.DEFAULT);
    return String.format("data:text/html;charset=utf-8;base64,%s", base64Data);
}

Advanced Browser Features


Handling Web Page Life-cycle and Other Events


There are several events associated with the web page life-cycle. For example there are events associated with web page loading being started/stopped. There are also events for web page errors as well as a login request from the web page. These web page events are handled via a WebViewClient. Your browser should, at a minimum, provide feedback to the user when a web page starts loading and when it stops loading. Here's an example of how you can handle those events:

protected WebViewClient getWebViewClient()
{
    return new WebViewClient()
    {
        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon)
        {
            /** provide feedback using a spinner or some other dialog to  
the user to let them know that you're doing some work **/
        }

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

            /** hide the feedback that was provided to the user **/
        }
    };
}


External Browser Interactions


One of the more nuanced aspects of displaying web content in your app is when the web content wants to do something external to the current browser. In order to interact with the browser for some of these more advanced features you need to use a WebChromeClient. For example, playing back HTML5 video fullscreen is done using the WebChromeClient's onShowCustomView method. Other examples of advanced external browser interactions are reporting debug messages to the Javascript console, opening/closing additional browser windows, displaying messages from Javascript, and etc.

It's important to note that there is only one WebChromeClient. Because of this you'll likely want to subclass WebChromeClient if you plan to provide many of these advanced features in your app. Here's an example of creating a WebChromeClient that can update the progress indicator of a Fragments Activity.

private WebChromeClient getWebChromeClient()
{
    return new WebChromeClient()
    {
        public void onProgressChanged(WebView view, int progress)
        {
            if(getActivity() != null)
            {
                getActivity().setProgress(progress * 100);
            }
        }
    };
}

For a more detailed example of integrating a basic web browser into your app, see the BrowserFragment in my open source RSS Reader Android app.

No comments:

Post a Comment