Previous  | Next  | Home

WebView Demo


 

Let's create our first Android project (see this overview of how to create an Android project for more detail). We shall start by doing something rather simple: We will put two buttons on the screen, and then have the buttons load particular Web addresses into new screens using the Android WebView class when they are pressed. Although rudimentary, this will already teach us a lot about how to build Android applications. In this one example we shall gain experience in how to

In short, when we finish this project we will have already touched on many basics of Android development. So let's get started!

 

Creating the Project in Eclipse

Open Eclipse and click File > New > Android Project. Fill out the resulting window as in the following figure,



(except that you should substitute your namespace for com.lightcone in the package name and in what follows) and click Finish.


Namespaces are explained further in Creating an Android Project. Note the warning in the above figure that the API level for the selected SDK target does not match the Min SDK Version. This is just a warning and we will ignore it because we are choosing to build against Android 2.2, but are indicating that we intend to write the program so that it will run under Android versions as early as 1.5 (API level 3). Of course this is just a declaration of intention at this point. We shall have to take care to use only code that is compatible with Android 1.5, and confirm with testing when we are finished that it will indeed run under Android versions from 1.5 to 2.2.)

A project WebViewDemo should now appear in the left panel of the Eclipse interface. Click the right triangle to open it, and then click successive right triangles to open WebViewDemo/src/com.lightconeinteractive/webviewdemo. There should be a single file, WebViewDemo.java showing. Double click it to open the file in the Eclipse editing window:



Thus Eclipse has generated automatically a stub for the WebViewDemo class:


    package com.lightcone.webviewdemo;
    import android.app.Activity;
    import android.os.Bundle;

    public class WebViewDemo extends Activity {
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
        }
    }

This is a short segment of code, but it already does a lot. We (through our loyal sidekick Eclipse) have now:

Before proceeding, if the import commands at the top of this file are folded up in the Eclipse code listing, click the + sign to their left to expand them.

 

Adding OnClickListener

Let us now modify this stub so that it will do what we wish. First, since we are going to add button widgets to the page for which we will need to listen for and process click events, we have the class implement the OnClickListener interface by modifying the first line defining the class to read


    public class WebViewDemo extends Activity implements OnClickListener {  
 

The Eclipse display of the file should now look like the following figure



Notice that

  1. there is a small red x to the left of line 6, indicating an error, and that

  2. in that line OnClickListener is underlined with a wavy red line, indicating that it is the source of the error (we are assuming that Project > Build Automatically is checked so that Eclipse attempts to build as we make changes).

If we hover the mouse over the red x we get the popup message "OnClickListener cannot be resolved to a type", and if we hover over the wavy red underline we get a number of suggested possible quick fixes for the problem, as illustrated in the following figure.



Since OnClickListener is an Android interface, the most likely problem is just that we have not imported the appropriate Android classes and indeed the first two options for quick fixes indicate that we should import android.view.View or android.content.DialogInterface. We can insert one of these import statements manually, but Eclipse will do it for us: clicking on the first choice, we see that Eclipse automatically inserts a new import statement

 

    import android.view.View.OnClickListener;

(If the import statements are folded up, click the + beside them to expand them.) There is still a red x, but now

  1. it is WebViewDemo that is underlined by a wavy red line, and

  2. if we hover over the red x we see the popup message "The type WebViewDemo must implement the inherited abstract method View.OnClickListener.onClick(View)".

If we then hover over WebViewDemo we get the following popup window.



If we select the quick fix "Add unimplemented method", Eclipse will fix it for us by adding a stub for the method and a required import. Now our code reads


    package com.lightcone.webviewdemo;
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.View;
    import android.view.View.OnClickListener;

    public class WebViewDemo extends Activity implements OnClickListener {

        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
        }

        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
        }
    }

where the added code is indicated in red. This should compile with no error messages because a stub for the method onClick(View v) has been inserted (with a "TODO" comment, indicating that this is just a stub satisfying the formal requirements for the interface, but we still must add the code appropriate to our goals).


An interface in Java is a reference type similar to, but not the same as, a class. An interface cannot contain any method bodies, only constants, nested types, and method signatures (methods without bodies). An interface cannot be instantiated; it can only be
  • implemented (by classes), or

  • extended (by other interfaces).
An abstract method is one that is declared without an implementation (with the expectation that the implementation will be supplied by something else). If a class contains abstract methods, it itself must be declared abstract. All methods of an interface are implicitly abstract.

When a class implements an interface, it must provide a method body for each of the methods declared in the interface, or the class must be declared abstract. Notice that these were the two quick-fix options suggested above by Eclipse:
  1. add the unimplemented method onClick(View v), or

  2. declare the class WebViewDemo abstract
for the error introduced when we implemented the OnClickListener interface.

 

Eclipse Is Only a Faithful Servant

Obviously Eclipse has been very helpful in laying out the basic initial structure of our class, but it has only done what a good programmer would have done by a manual analysis.

  1. When we added "implements OnClickListener" it was necessary to import the Android classes defining this interface, and since an interface in Java generally requires that certain methods be implemented (even if only as stubs), we had to add those required methods.

  2. In this case, if we had consulted the Android documentation by going to the Android developer docs and putting "OnClickListener" in the search field we would have gotten two options:


  3. Choosing the first we get documentation for the interface View.OnClickListener. So we must import android.view.View.OnClickListener.

  4. Furthermore, we find on the same page that this interface has the single method public abstract void onClick (View v).

  5. Thus, by the general rules for Java interfaces, we must provide an implementation of this method if we employ the interface View.OnClickListener (or we must declare the class abstract).

All that Eclipse has done is carry out this sequence of programmer deductions rather automatically.

 

Layout of the User Interface

Now we wish to

  1. lay out a page containing some button widgets,

  2. define event handlers for the buttons, and

  3. implement some actions when the buttons are clicked.

First let's define some string and color resources that we will need. Open in the left panel res/values and double-click on the file strings.xml to open it (if the file text is not displayed, click the strings.xml tab at the bottom). Edit the file to read


    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="hello">WebViewDemo</string>
        <string name="app_name">WebView Demo</string>
        <string name="button1_label">Mojito Recipe</string>
        <string name="button2_label">GOES 8 Satellite</string>
    </resources>

and save the strings.xml file. Now create a new file under res/values called colors.xml:

  1. Right-click on values and select New > Android XML File.

  2. In the resulting window, set (many of these will already be set)


    (ignore the other fields).

  3. Click Finish.

This will create a new file in the left panel under res called colors.xml. Double-click to open and edit it to read as follows:


    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <color name="background">#aa669999</color>
    </resources>

Save the colors.xml file.


In Android, 32-bit colors with alpha-opacity are specified by a sequence #AARRGGBB where
  • AA is a hexadecimal (hex) number defining the alpha-opacity,

  • RR is a hex number defining the amount of red,

  • GG is a hex number defining the amount of green, and

  • BB is a hex number defining the amount of blue.
The hex numbers range from 00 to ff (that is, 0-255 decimal), with 00 meaning no contribution for the component and ff meaning full contribution. For example, opaque black would be specified by #ff000000 (full opacity, but no RGB colors). It is also possible to define
  • 24-bit color (#RRGGBB),

  • 16-bit color with alpha-opacity (#ARGB),

  • and 12-bit color (#RGB).
Finally, the Color class has a few colors predefined as static constants (for example, Color.MAGENTA or Color.TRANSPARENT).


You can also create these XML and Java files manually using a text editor by navigating to the appropriate directories in the directory tree of your workspace on your disk that corresponds to the project tree in Eclipse. Be very careful with editing XML however, as it requires exact syntax and form or it will generate an error message when you try to compile. For example, any whitespace before the first XML line may generate an error message.

We are now ready to lay out the opening screen of our app. Open res > layout and double-click on main.xml to open it. Edit it to read


    <?xml version="1.0" encoding="utf-8"?>
        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:orientation="vertical"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:background="@color/background"
            android:padding="30dip" >

            <TextView  
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:text="@string/hello"
            android:layout_gravity="center"
            android:layout_marginBottom="25dip"
            android:textSize="24.5sp" />

            <Button
            android:id="@+id/button1"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="@string/button1_label"
            android:textSize="18sp" />
            
            <Button
            android:id="@+id/button2"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="@string/button2_label"
            android:textSize="18sp" />

        </LinearLayout>

Save the file when you have completed the changes. In this file

The units for sizes and distances used in this layout are discussed in the following box, and a more complete discussion of XML attributes may be found in the Android Declaring Layout document. A comprehensive list of XML attributes in Android may be found at R.attr.


Notice the size units dip, sp in these layout elements. Generally in Android layout sizes can be specified in
  • pixels (px),

  • millimeters (mm),

  • inches (in),

  • points (pt), which are equal to 1/72 of an inch,

  • density-independent pixels (dp or dip), or

  • scale-independent pixels (sp).
It is good practice to use sp for font sizes and dip for other user interface sizes rather than absolute units like inches or pixels, since then your interface will scale better on different devices with different screen resolutions.

We now should have an opening screen laid out that will display two buttons. Execute the program on an emulator:

  1. Right-click on WebViewDemo in the left panel.

  2. Select Run As > Android Application.

  3. Launch and/or choose an appropriate emulator if prompted to do so; try initially an emulator for Android 2.2.

You should see on the emulator a screen like that shown below,



where the strings displayed as titles and button labels were those defined in strings.xml, and the background color was defined in colors.xml.

 

Using Intents to Launch New Activities

This is now a user interface with a label and two buttons, but it doesn't do anything except that the buttons may change color momentarily to indicate that they have been clicked by the mouse on the emulator (or pressed by a finger on an actual device). Now we shall add event handlers to cause actions to be taken when the buttons are clicked. When each button is clicked, we wish to launch a new screen (that will replace the original screen) on which we are going to do something. As we discussed in Android User Interfaces the standard way to do that is to launch an intent, with the corresponding screen defined by a new Java class. Let's first create the basic stub for the new class, leaving some details to be filled in later, and then define the event handlers that open the new screen.

Right click on src/com.lightcone.webtutorialdemo and select New > Class. On the resulting screen, fill in the blanks as in the following figure and click Finish.





This should create a new file Webscreen.java under src. Edit this file so that it has the content


    package com.lightcone.webviewdemo;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.webkit.WebView;
    import android.util.Log;
    
    public class Webscreen extends Activity {

        public static final String URL = "";

        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.webscreen);
        }
    }

(We've added several imports and defined a String variable URL that we will need shortly.) If you save this file

  1. There will be a red x on the setContentView line, with a wavy red line under R.layout.webscreen.

  2. Hovering over these with the mouse will indicate that the error is associated with a missing R.layout.webscreen source.

  3. The R. notation indicates that this is a resource that Java expects Android to supply at compile time.

  4. The full notation R.layout.webscreen indicates that it expects this resource to reside in a file res/layout/webscreen.xml, which of course has not been created yet.

At compilation, each XML resource file is compiled into a View resource.
  • The resource is loaded by calling setContentView(int layoutResID) from the Activity.onCreate() callback, which is called by the Android framework at launch of the app; see the lifecycle diagram in Application Lifecycles.

  • The reference layoutResID to your layout resource is passed as an argument to setContentView in the form R.layout.layout_file_name, where layout_file_name is the name of the file (without extension) that you have placed in res/layout containing the layout specification.
For a more extensive discussion, see the Android Declaring Layout document.

So we fix this problem by adding a file webscreen.xml that will define the layout of the new screen:

  1. Right-click on res/layout and select New > Android XML File.

  2. On the resulting screen choose a file name webscreen.xml, the type of resource a Layout, and a LinearLayout for the XML root element.

  3. Check that the Project is WebViewDemo and the Folder is /res/layout.

Click Finish and edit the resulting file (webscreen.xml under res/layout) so that it reads


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

If you save this you should now find that the error indicated for Webscreen.java has disappeared because now the resource webscreen.xml (which is supplied as R.layout.webscreen by Android at compilation) is defined.

 

Event Handlers

Now we add event listeners and edit the onClick method of WebViewDemo.java to add the corresponding event handlers so that the file WebViewDemo.java reads


    package com.lightcone.webviewdemo;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.os.Bundle;
    import android.view.View;
    import android.view.View.OnClickListener;
    
    public class WebViewDemo extends Activity implements OnClickListener {
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);

            // Add Click listeners for all buttons
            View firstButton = findViewById(R.id.button1);
            firstButton.setOnClickListener(this);
            View secondButton = findViewById(R.id.button2);
            secondButton.setOnClickListener(this);

        }
    
        // Process the button click events
        @Override
        public void onClick(View v) {

            switch(v.getId()){
                case R.id.button1:
                    Intent j = new Intent(this, Webscreen.class);
                    j.putExtra(com.lightcone.webviewdemo.Webscreen.URL, 
                        "http://eagle.phys.utk.edu/guidry/recipes/mojito.html");
                    startActivity(j);
                break;
                
                case R.id.button2:
                    Intent k = new Intent(this, Webscreen.class);
                    k.putExtra(com.lightcone.webviewdemo.Webscreen.URL, 
                        "http://www.ssec.wisc.edu/data/east/latest_eastir.gif");
                    startActivity(k);
                break;      
            }
		
        }
    }

where the added lines are indicated in red. A number of important things are happening in this compact segment of code:

The putExtra method of the Intent class is a way to pass variables to the class that will be launched by the intent. In this case the value of a string corresponding to a web address (the first argument of putExtra) is communicated by assigning its value to a string variable defined in the class launched by the intent (the second argument), but variables of other types may be passed in this way also. See the various overloaded versions of putExtra(String name, Bundle value) documented under the class Intent. Note that the variable name in the putExtra arguments must have a package prefix (e.g., com.lightcone.myClass rather than myClass).

This should now compile, but if we launch it on the emulator and press one of the buttons we get an error popup:


Sorry! The application WebViewDemo (process com.lightcone.webviewdemo) has stopped unexpectedly.

Checking the logcat file (see The Purr of the Logcat) we find the source of the problem immediately:


06-12 22:57:11.302 I/ActivityManager(59):Starting activity:Intent { cmp=com.lightcone.webviewdemo/.Webscreen (has extras)}
06-12 22:57:11.302 D/AndroidRuntime(399):Shutting down VM
06-12 22:57:11.302 W/dalvikvm(399): threadid=1:thread exiting with uncaught exception (group=0x4001d800)
06-12 22:57:11.342 E/AndroidRuntime(399):FATAL EXCEPTION: main
06-12 22:57:11.342 E/AndroidRuntime(399):android.content.ActivityNotFoundException: Unable to find explicit activity class
{com.lightcone.webviewdemo/com.lightcone.webviewdemo.Webscreen}; 
have you declared this activity in your AndroidManifest.xml?

This is because Android requires that all activities must be declared in the AndroidManifest.xml file. To fix it,

  1. open AndroidManifest.xml (select the .xml tab at the bottom if the XML is not displayed) and

  2. add an appropriate <activity></activity> tag within the <application></application> tag

so that the manifest file now reads


    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.lightcone.webviewdemo"
        android:versionCode="1"
        android:versionName="1.0">
        <application android:icon="@drawable/icon" android:label="@string/app_name">
            <activity android:name=".WebViewDemo"
                android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            </activity>
            <activity android:name=".Webscreen" android:label="Web"> </activity>
        </application>
        <uses-sdk android:minSdkVersion="3" />
    </manifest> 

where the added line is highlighted in red. If we execute in the emulator, clicking on the first button loads a new blank screen, hitting the return button on the phone keyboard returns us to the first screen, and clicking on the second button again loads a new blank screen. Now we only have to add content to these blank screens.

 

Adding WebViews

First, let's check that the variables are being passed by the putExtra command to the new activities launched with the intent. Edit the file Webscreen.java so that it reads


    package com.lightcone.webviewdemo;
    import android.app.Activity;
    import android.os.Bundle;
    import android.webkit.WebView;
    import android.util.Log;

    public class Webscreen extends Activity {
            
        public static final String URL = "";
        private static final String TAG = "Class Webscreen";
        
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.webscreen);   
            String turl = getIntent().getStringExtra(URL);
            Log.i(TAG, "Recipe url = "+turl);     
        }
    }

The getIntent().getStringExtra(URL) now assigns the string variable passed using putExtra, and the Log.i allows us to check whether the variable is being passed correctly

.
The method getIntent() of the Activity class returns the Intent that started the activity. The method getStringExtra(String) of the Intent class takes a String argument corresponding to a variable passed to the Intent using putExtra() and returns its String value. Thus the net effect of the chained expression

    String turl = getIntent().getStringExtra(URL);
is to use the getIntent() method of Webscreen (which it inherits from its superclass Activity) to return the Intent that launched Webscreen, and then to use the getStringExtra method of that Intent to return the String corresponding to the variable URL that was passed to the Intent using the Intent method putExtra(), and finally assign it to the local String variable turl.

Executing the app in the emulator after these changes and clicking the two buttons successively we find in the logcat output


06-12 23:40:19.131 I/ActivityManager(59):Starting activity: Intent { cmp=com.lightcone.webviewdemo/.Webscreen (has extras)}
06-12 23:40:19.231 I/WebscreenClass(501): URL=http://eagle.phys.utk.edu/guidry/recipes/mojito.html
06-12 23:40:19.521 I/ActivityManager(59): Displayed activity com.lightcone.webviewdemo/.Webscreen: 354 ms (total 354 ms)
06-12 23:40:22.071 W/KeyCharacterMap(501): No keyboard for id 0
06-12 23:40:22.071 W/KeyCharacterMap(501): Using default keymap: /system/usr/keychars/qwerty.kcm.bin
06-12 23:40:23.361 I/ActivityManager(59): Starting activity: Intent{ cmp=com.lightcone.webviewdemo/.Webscreen (has extras)}
06-12 23:40:23.441 I/WebscreenClass(501): URL=http://www.ssec.wisc.edu/data/east/latest_eastir.gif

Thus, the web addresses are indeed being passed to the Webscreen instance. Now finally, let us embed a WebView in the screen generated by the Webscreen object so that we can display these web addresses as web pages. Add code to Webscreen.java so that it reads


    package com.lightcone.webviewdemo;
    import android.app.Activity;
    import android.os.Bundle;
    import android.webkit.WebView;
    import android.util.Log;

    public class Webscreen extends Activity {
            
        public static final String URL = "";
        private static final String TAG = "WebscreenClass";
        
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.webscreen);
            
            String turl = getIntent().getStringExtra(URL);
            Log.i(TAG, " URL = "+turl);
            
            WebView webview = new WebView(this);
            setContentView(webview);
    
            // Simplest usage: No exception thrown for page-load error
            webview.loadUrl(turl);  

        }
    }

This should compile, but when we try to execute in the emulator we find that the buttons attempt to load webpages but we get error messages in both cases that the webpage is not available. There could be many reasons (like the network being down) for such messages but in this case the explanation is quite basic: we have not yet done one final thing that is required for an Android application that tries to access the internet. It must be given explicit permission to do so, and this is done by adding to the AndroidManifest.xml file a permission line (which must be within the manifest tag)


    <uses-permission android:name="android.permission.INTERNET" />

So that the AndroidManifest.xml file now reads


    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.lightcone.webviewdemo"
        android:versionCode="1"
        android:versionName="1.0">
        <application android:icon="@drawable/icon" android:label="@string/app_name">
            <activity android:name=".WebViewDemo"
                android:label="@string/app_name">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <activity android:name=".Webscreen" android:label="Web"> </activity>
        </application>
        <uses-sdk android:minSdkVersion="3" />
        <uses-permission android:name="android.permission.INTERNET" />
    </manifest> 

Now if we execute in the emulator or on a device we obtain the left figure below when we click on the first button and the right figure below when we click on the second button (these are screen shots from the program running under Android 1.5 on a Motorola Backflip phone).



So our application is indeed loading and displaying---under our complete programming control---arbitrary webpages using an extension of the WebView class.


Each time you have run WebViewDemo on an emulator or device, the project has been installed on that device or emulator (through the .apk file). If you look in the apps folder of the device or the emulator, there should now be an icon labeled WebView Demo, and you can launch the app by pressing that icon.




By default, when a project is created in Eclipse a file called icon.png that corresponds to a stock Android image (see the icon for WebView Demo in the above figure) is placed in all res/drawable directories and a line

 <application android:icon="@drawable/icon" android:label="@string/app_name">

is inserted in the AndroidManifest.xml file for the project. This causes the project to use the image in the icon.png file as the app icon. You can customize your app by placing your own icon file in the res/drawable directory and changing the android:icon tag in the manifest file shown above to point to the new image. (Note that the icon file must be in .png, .jpg, or .gif format, with .png preferred, and that only lower-case letters, numbers, and the symbols _ and . may appear in the file name.)

This customization is optional for development but you will certainly want to do this if you release your app for others to use. There are some tricks of the trade associated with effective icon design for small screens. If you decide to make a custom icon, consult the Android Icon Design Guidelines for guidance.


The complete project for the application described above is archived at the link WebViewDemo.



Exercises

1. By default the WebView class implements a view with an embedded webpage that provides no browser-like widgets, does not enable JavaScript, and ignores web page errors (this is the mode in which we employed it). Add to the embedded webview for WebViewDemo some of the customizations such as error handling, settings, and progress notification that are discussed in the WebView documentation. [Solution]

2. Use the documentation of the WebSettings and WebView classes to modify WebViewDemo so that zoom controls are built into the displayed webpage. [Solution]


Previous  | Next  | Home