In A Simple First App we created our first simple app. Let's now build a somewhat more sophisticated Android project. We shall put two buttons on the screen, and then have the buttons load particular web addresses into new screens when they are pressed. This is more advanced than our first simple project because now we must manage more than one button on the screen, and because in the Simple First App project we displayed a webpage using the default web browser built into the system, but in this project we shall display the content of web addresses in a basic browser that we shall create and manage ourselves programatically using the Android WebView class.
Although all of these steps are fairly rudimentary, completing them 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!
Following the general procedure in Creating a New Project, either choose Start a new Android Studio project from the Android Studio homepage, or from the Android Studio interface choose File > New > New Project. Fill out the fields in the resulting screens as follows,
Application Name:
WebViewDemo
Company Domain:< YourNamespace > Package Name: <YourNamespace> . webviewdemo Project Location: <ProjectPath> WebViewDemo Target Devices: Phone and Tablet; Min SDK API 15 Add an Activity: Empty Activity Activity Name: MainActivity (check the Generate Layout File box) Layout Name: activity_main |
where your namespace should be substituted for <YourNamespace> (com.lightcone in my case) and <ProjectPath> is the path to the directory where you will store this Android Studio Project (/home/guidry/StudioProjects/ in my case).
A project WebViewDemo should now appear in the left panel (project panel) of the Android Studio interface, as in the figure below. (Note: choose Android in the drop-down menu at the top of the project panel if it is not already chosen.) If it is not already open, open the file MainActivity.java in the main editing window, as illustrated in the following figure:
We see that Android Studio has generated automatically a stub for the MainActivity class:
package <YourNamespace>.webviewdemo; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
This is a short segment of code, but it already does a lot. We (through our loyal sidekick Android Studio) have now:
The @Override is an annotation used to mark methods that override a method declaration in a superclass. Its use is optional but compilers produce an error if a method annotated with @Override does not actually override a method in a superclass, so it provides a useful automatic consistency check. |
Before proceeding, if the import commands at the top of this file are folded up in the AS code listing, click the + sign to their left to expand them.
If you have chosen to use version control for your projects (I recommend it strongly!), now would be a good time to commit this project to version control so that from this point onward your changes can be tracked and stored by the version control system. |
Let us now modify this stub so that it can do some useful things. 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 MainActivity extends AppCompatActivity implements OnClickListener {
In the AS display the word "OnClickListener" turns red, indicating a compile error. If you click with the mouse on the word and execute Alt-Enter from the keyboard, a popup suggesting possible fixes should appear, as 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 two options for quick fixes indicate that we should import android.view.View or android.content.DialogInterface. The first choice is the appropriate one in this context. We can insert it manually, but Android Studio will do it for us: clicking on the first choice, we see that AS automatically inserts a new import statement
import android.view.View;
(If the import statements are folded up, click the + beside them to expand them), and OnClickListener is converted to View.OnClickListener. But now a new compile error appears, indicated by a wavy red line under the entire line containing View.OnClickListener and a red lightbulb above the line. Clicking on the line to give focus and executing Alt-Enter gives the popup with suggested fixes shown in the following figure.
The choice that we want is "Implement methods", which will implement a method that is required by OnClickListener. Select that and the following window should appear:
The choice onClick(v:View):void is the method required (a good thing, since it is the only option). Select it and click OK. Android Studio will add some more code, and now the errors should disappear, indicating that the project has compiled successfully. The code now reads
package <YourNamespace>.webviewdemo; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; public class MainActivity extends AppCompatActivity implements View.OnClickListener { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public void onClick(View v) { } }
where the added code is indicated in red. This compiles with no error messages because a stub for the method onClick(View v) has been inserted that satisfies the formal requirements for the OnClickListener interface. However the added onClick method does not do anything yet and 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
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 Android Studio:
|
Obviously AS 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.
All that Android Studio has done is carry out this sequence of programmer deductions rather automatically.
Now we wish to
First let's define some string and color resources that will be needed. Open the file strings.xml under res/values and edit it to read
<resources> <string name="app_name">WebViewDemo</string> <string name="button1_label">Chili Recipe</string> <string name="button2_label">NOAA Tropical Satellite</string> </resources>
Then open the file colors.xml and edit it to read as follows:
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#3F51B5</color> <color name="colorPrimaryDark">#303F9F</color> <color name="colorAccent">#FF4081</color> <color name="background">#fffcfcf2</color> </resources>
where the numbers are hexadecimal (hex) numbers specifying colors.
In Android, 32-bit colors with alpha-opacity are specified by a sequence #AARRGGBB where
|
You can also edit these XML and Java files manually using a text editor if you choose, by navigating to the appropriate directories in the directory tree of your workspace on your disk that corresponds to the project tree in Android Studio. 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 activity_main.xml to open it. In the Design view, set the theme to Holo Light. Then edit the file in the Text view to read
<?xml version="1.0" encoding="utf-8"?> <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="16dp" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp" android:background="@color/background" tools:context="<YourNamespace>.webviewdemo.MainActivity"> <Button android:id="@+id/button1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/button1_label" android:textSize="18sp" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_marginTop="56dp" /> <Button android:id="@+id/button2" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/button2_label" android:textSize="18sp" android:layout_below="@+id/button1" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" /> </RelativeLayout>
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 Layout document. A comprehensive list of XML attributes in Android may be found at R.attr.
Notice the size units dp, sp in these layout elements.
Generally in Android
layout sizes can be specified in
|
We now should have an opening screen laid out that will display two buttons. Execute the program on an emulator or a phone or tablet:
For example, the following prompt asks to choose among one of two emulators and a Huawei Nexus 6P phone attached to the computer by USB
Choose and click OK. You should see on the emulator or device a screen like that shown below (which is a screenshot from a Huawei Nexus 6P phone),
where the strings displayed as titles and button labels were those defined in strings.xml, and the background color was defined in colors.xml.
You can easily take screenshots from emulators or actual devices in your own development using Android Studio. Instructions are given in the Appendix Device Screen Shots. |
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 app/java/<YourNamespace>.webviewdemo in the AS project pane and select New > Java Class. In the resulting popup window, give the new class the name Webscreen,
and click OK. This should create a new file Webscreen.java. Edit this file so that it has the content
package <YourNamespace>.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); } }
(Several imports have been added and a String variable URL has been defined that we will need shortly.)
At compilation, each XML resource file is compiled into a
View resource.
|
So this problem can be fixed by adding a file webscreen.xml that will define the layout of the new screen:
Click Finish. The resulting file (webscreen.xml) reads
<?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"> </LinearLayout>
and you should 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.
Now we add event listeners and edit the onClick method of MainActivity.java to add the corresponding event handlers so that the file MainActivity.java reads
package <YourNamespace>.webviewdemo; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.content.Intent; public class MainActivity extends AppCompatActivity implements View.OnClickListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_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(<YourNamespace>.webviewdemo.Webscreen.URL, "http://eagle.phys.utk.edu/guidry/recipes/chili.html"); startActivity(j); break; case R.id.button2: Intent k = new Intent(this, Webscreen.class); k.putExtra(<YourNamespace>.webviewdemo.Webscreen.URL, "http://www.ssd.noaa.gov/goes/east/tatl/vis-l.jpg"); startActivity(k); break; } } }
where the added and modified lines are indicated in red and you must replace <YourNamespace> with your namespace. A number of important things are happening in this compact segment of code:
Context is an abstract class implemented at Android system level that provides an interface to global information about an application environment. One common use of Context is to broadcast and receive Intents and launch Activities. |
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., <YourNamespace>.myClass rather than myClass). |
This should now compile, but if we launch it on the emulator or device and press one of the buttons we get an error popup:
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,
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="<YourNamespace>.webviewdemo"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="WebViewDemo" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <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> </manifest>
where the added line is highlighted in red. If the app is executed now, clicking on the first button loads a new blank screen, hitting the return button on the phone takes us back to the first screen, and clicking on the second button again loads a new blank screen. We are getting close! Now we just need to add content to these blank screens.
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 <YourNamespace>.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, "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
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/chili.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.ssd.noaa.gov/goes/east/tatl/vis-l.jpg
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 <YourNamespace>.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 and outside the application 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="<YourNamespace>.webviewdemo"> <uses-permission android:name="android.permission.INTERNET" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="WebViewDemo" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <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> </manifest>
Now execution in the emulator or on a device gives the left figure below for a click on the first button and the right figure below for a click on the second button. (The left image is a screen shot from a Nexus 6P phone running Android 6.0 and the right image is a screen shot from a Galaxy Nexus phone running Android 4.2.)
So our application is indeed loading and displaying---under our complete programming control---arbitrary webpages using an extension of the WebView class.
Each time WebViewDemo has been run on an emulator or device the project has been installed on that emulator or device as an app.
If you look in the apps folder (open by pressing the circle with 6 dots in it near the bottom of the homescreen),
there should now be an icon labeled WebViewDemo, and you can launch the app by pressing that icon (and it will remain on the device unless uninstalled).
By default, when a project is created in Android Studio a file called ic_launcher.png that corresponds to a stock Android image (see the green bugroid icon for WebView Demo in the above figure) is placed in all res/mipmap/ic_launcher.png directories corresponding to images to be used for devices of different screen resolution, and an attribute of the form android:icon="@mipmap/ic_launcher" is inserted in the AndroidManifest.xml file: This causes the project to use the image in the ic_launcher.png file as the app icon (at a resolution appropriate for the device). Your app can be customized by replacing the default icon in the following way. In Android Studio with the project open, select File > New > Image Asset. In the resulting popup window,
Admittedly my picture is hardly improvement over the default green bugroid, but this example shows the general procedure to replace the default icon. (You can switch back to the default icon by changing the icon attribute in AndroidManifest.xml back to ic_launch.) Use of customized icons is optional for development but you will certainly want to do this if you intend to 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 in the Iconography document for guidance. |
The complete project for the application described above is archived at the link WebViewDemo. Instructions for installing it in Android Studio may be found in Packages for All Projects. |
Last modified: July 25, 2016