Intro
In this lab we will be learning how to use and extend the Android user interface library. In a number of ways it is very similar to the Java Swing library, and perhaps just as many ways different. While being familiar with Swing may help in some situations, it is not necessary. It is important to note that this lab is meant to be done in order, from start to finish. Each activity builds on the previous one, so skipping over earlier activities in the lab may cause you to miss an important lesson that you should be using in later activities.
Objectives
At the end of this lab you will be expected to know:
Activities
For this lab we will be creating a "Joke List" application. It is a simple app that allows a user to view and edit a list of jokes. All tasks for this lab will be based off of this application. Over the course of the lab you will be iteratively refining and adding functionality to the Joke List app. With each iteration you will be either improving upon the previous iteration's functionality, or you will be implementing the same functionality in a different way.
IMPORTANT:
You will be given a Skeleton Project to work with. This project contains all of the java and resource files you will need to complete the lab. Some method stubs, member variables, and resource values and ids have been added as well. It is important that you not change the names of these methods, variables, and resource values and ids for the sake of testing.
In the Skeleton Project will be several test files. These unit tests will be used to evaluate the correctness of your lab. You have complete access to these test cases during development, which gives you the ability to run these tests yourself. You will learn how to create an Android Test Project, and will insert these test files into it for testing purposes.
Contents
1. Setting Up
1.1 Creating the Project
To begin, you will need to download and extract the skeleton project for the JokeList application.
Next you will need to set up a "Joke List" Android project for this app, and the skeleton project will serve as that Android project. Since the skeleton project was created in Eclipse, the easiest thing to do is to import this project into Eclipse.
1.2 Fill in the Joke Class
Throughout the lab you will be working with the Joke object class. It will serve as the data behind the Android components that will be visible on the application screen. You can see that it is a Plain Old Java Object--or POJO for short. All it has is
Open the Joke.java file under the src folder in the edu.calpoly.android.lab2 package. At first glance, look at the member variables. This class will be used to encapsulate two items pertaining to jokes:
Begin by filling in this class:
1.3 Create Android Test Project
To make sure the Joke class has been implemented correctly, several unit test files have been provided to you in the Skeleton Project, under the test folder. Now we will place these test files in a proper Android Test Project. But first, we must create the Android Test Project.
Why are we creating a brand new project just for testing?
This is one of two ways to execute this practice. We will demonstrate and stick with the other option in the next lab and the remainder of the labs. However, the choice of how to run tests when doing your own Android development is up to you. This question is addressed further here.
Now you have an empty Android Test Project in your Package Explorer as well. It looks very similar in structure to the Skeleton Project; this is done on purpose. Check the src folder in the Android Test Project and you will notice a new package: edu.calpoly.android.lab2.test. It is empty, but we will now fill it with tests!
There, now the tests are all set up. Now we'll take a quick look at the settings for running tests. For now, we will just test the Joke.java file to make sure it is implemented correctly.
Now when you click Run after clicking on or anywhere inside the lab2test<userid> project in the Package Explorer, the application will attempt to initialize and deploy, and tests will be run. So what are we waiting for? Let's run them!
This should open up a JUnit tab next to the Package Explorer. You'll see some red--this is expected, since you haven't implemented the SimpleJokeList.java file yet. If you've implemented every method in the Joke.java file correctly, you should see something similar to the following (click image for full size):
The tests in the above image were initiated using a Droid 3 physical device.
Now let's just run the JokeTest tests, since we know that SimpleJokeListTest is not implemented yet.
This will only run JokeTest.java and ignore SimpleJokeListTest.java. If you've implemented every method in the Joke.java file correctly, you should see the following (click image for full size):
Success!
If you see a red bar across the top, this means that you've failed one or more tests. The tab should show how many tests failed, and which tests you failed:
Need to fix up a few things.
Feel free to open up the JokeTest.java file to look at what exactly is being tested. Make the appropriate corrections to Joke.java and rerun the tests until you've passed all of them. We'll worry about the SimpleJokeListTest.java tests later.
1.4 Retrieving the Joke Resources Strings
The skeleton project has been pre-populated with an array of three different String resources you can use as sample jokes. For a complete background on Resources and how to properly use them you can read the Resources Overview.
You need to display these jokes when the application starts up. When an Activity first starts up, its onCreate() method is always called. This method allows you to initialize the Activity. Right now you will only be initializing local variables to hold the jokes that you just entered:
When creating a new Activity you will almost always override the onCreate() method. The onCreate() method, as you will see later, is the place where you will be creating your User Interfaces, binding data to different UI controls, and starting helper threads (this will not be covered in this lab). Also, take note of the "Bundle savedInstanceState" parameter that gets passed in. This variable contains information on the previous state of the UI. As you may have assumed, this means that you are able to save the state of the UI before it closes so that it can be initialized to the same state the next time it is created.
2. LogCat
Wouldn't it be wonderful to debug your Android code while programming? Android has something for that, too.
Android uses a special debugging framework called LogCat, which will show every message broadcast from every Activity on the Android device in real time! You can use this framework to send messages from your own application containing various useful information for debugging purposes, such as print statements.
Why use a special debugging framework? It allows for more flexible, fleshed-out feedback tied directly to Android. It helps contain all debugging in a single location, instead of spread out across multiple areas. For example, inserting print statements into code (a very common, effective debug method) to print messages out to the Console (usually located in the bottom tabbed window in Eclipse) will reveal nothing when running an Android application. In the context of Android, what you usually see in the Console is status regarding Activity launching, but not any debug statements. Any Console output expected from an Android application will not appear! (Unless you are using an AVD).
Note: For more in-depth debugging of Android applications in Eclipse, read more about the Dalvik Debug Monitor Server (DDMS) and Java Debugger.
The LogCat tab.
You will see activity such as this in the LogCat tab (Click image for larger view):
Every message being sent from every process on the device, with details.
This is where every single message from the device will appear, including messages from your application (this includes fatal errors, which appear in red).
Now you will make your application generate some messages. You will send a message to LogCat for each Joke that is added to the Joke List using the Log class.
Simple and elegant.
If you are ever confused on what your application is doing wrong, consider using LogCat even to just display information about your variables or to simply echo messages to make sure certain methods or lines of code are being reached.
Note: Be mindful of what message Level you use if you go on to publish applications! Debug is a safe Level for avoiding accidental publication of debug-level information.
3. Brief Background on View Classes
In Android, a user interface is a hierarchy composed of different View objects. The View class serves as the base class for all graphical elements, of which there are two main types:
Layouts are all subclasses of the ViewGroup class and their main purpose is to control the position of all the child views they contain. Layouts can be nested within other layouts as well, to create complex user interfaces. Some of the common layout objects you will use are:
Declaring the layouts for your user interface can be done dynamically (in code), statically (via an XML resource file, such as in Lab 1), or any combination of the two. In the following section you will build a set of user interfaces dynamically in code. In future labs, you will build a set of user interfaces in XML for static use as this is an overall better practice. It's good to know both ways!
4. SimpleJokeList
Work done in this section will be limited to the SimpleJokeList.java file. SimpleJokeList is an Activity class which displays a vertical scrollable list of all the Jokes in m_arrJokeList. Additionally, it has the ability to add jokes to m_arrJokeList via a text field and add a button that floats at the top of the screen.
4.1 Declaring Dynamic Layouts in Code
If you've ever built UIs in Java using Swing, this should be somewhat familiar. You generally want to try to define the layouts for your interface using XML when possible, but there are plenty of instances where it is still necessary to do it at run-time in code. For instance, you may want to dynamically change the layout based on some type of user input. Nevertheless, working with the actual code is good practice for understanding how the different Layout classes work and what sorts of interfaces all the various controls offer.
4.1.1 Display a Scrollable Vertical List
Your first Task is to simply display each of the jokes in the strings.xml resource file in a scrollable vertical list. When finished your application should look something like this:
Looks simple, doesn't it?
Fill in the initLayout() method in SimpleJokeList.java:
Update the addJoke() method:
Your AndroidManifest.xml is currently set up so that the SimpleJokeList Activity will launch on startup. Modify the Run Configuration to specifically launch the SimpleJokeList Activity:
It's a start, but it's a bit hard to tell where one joke ends and another begins...
The text is a little small, so use TextView's setTextSize(...) to increase the size of all jokes to 16 font. Note that there are two different method signatures for setTextSize(). Feel free to mess around with the font size and different TypedValue constants until the text looks large enough on the device you are running this application on.
It's somewhat hard to distinguish one joke from another, so let's alternate the background colors of each joke. We'll store the color values as resources:
You can use either the Resource Editor or you can directly add the following XML to the file:
<color name="light">#1F1F1F</color>
<color name="dark">#3D3D3D</color>
Back in SimpleJokeList, modify the background color of the TextViews by calling setBackgroundColor as you create them in your addJoke(...) method.
if you run the application now, the text may be too faded to read easily. Let's make the text completely white.
Run the application now, and the Jokes should be much more distinguishable with proper text coloring:
Now that's MUCH better!
Feel free to experiment with the text color and alternating background colors (you can even add more) so it is pleasant and satisfying for your own viewing purposes.
4.1.2 Adding Your Own Jokes
In this next task we will give the user the ability to enter their own jokes by adding a text field and a button to the UI. The text field and button should float at the top of the screen, above the list of jokes. When you scroll through the jokes, the text field and button should not scroll, but remain at the top of the screen.
In order to accomplish this we will walk through a process of nesting LinearLayouts within each other to achieve the desired effect. However, feel free to experiment and use whatever type of ViewGroup Layout you want, so long as the functionality and appearance remains the same. In the figure shown below, you can see how a root vertically-oriented LinearLayout containing a horizontally-oriented LinearLayout and the ScrollView of jokes can be combined to achieve the effect we want.
Green: Root ViewGroup. Gold: Child ViewGroups. Blue: Child View components.
Note that the app title, Joke List, is not actually a part of the first of two nested LinearLayouts. The first child ViewGroup in the above image was only extended to make room for the orientation arrow.
In this step you will only be updating your Layout. The new "Add Joke" button and text field won't work yet. You will add code to hook up those controls in the next section.
Run your application to test that you have properly setup your layout. You should see a text field and button at the top, followed by the list of jokes below. Trying to add jokes with the Add Joke button won't work yet.
4.2 Simple UI Event Listeners
Now that your UI has the components necessary to allow users to enter new jokes, you need to hook these components up. In this section you will define and register event listeners to handle adding new jokes.
Read the Android Developers Guide on Handling UI Events to get a detailed background on event handlers and listeners. In short, you use event listeners to respond to user interaction with your UI. There are two general steps for doing this. The first is to define an object that implements an interface for the type of interaction you want to respond. The second step is to register that object with the UI control you want to respond to. For example, to react to a particular Button being clicked, you have to register an object that implements the OnClickListener interface with the Button you want to listen to.
4.2.1 Adding a Button Listener
Setup the onClickListener for the "Add Joke" Button. When the user hits the "Add Joke" Button a new Joke object should be initialized with the text from the EditText field and added to your joke list. If the EditText is empty, then the Button should do nothing. In initAddJokeListeners():
m_vwJokeButton.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
//Implement code to add a new joke here...
}
});
InputMethodManager imm = (InputMethodManager)
getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(m_vwJokeEditText.getWindowToken(), 0);
4.2.2 Adding Key Listeners
Your next task is to add event listeners that respond to certain keys being pressed on your own. When the user is entering their joke into the EditText and presses either the "Enter/Return" key or the "DPad-Center/Track-Ball" key, the application should respond exactly the same as it does when the "Add Joke" Button is pressed.
Add this code to the initAddJokeListeners() method. Remember that after the joke gets added, the soft-keyboard should disappear. You'll want to use an anonymous inner class, declaring a new OnKeyListener() object and implementing the onKey(View, int, KeyEvent) method.
Hints:
Note: Now you probably see why we chose to not make SimpleJokeList implement the OnClickListener interface in the previous step. It is messy if SimpleJokeList acts as the listener handler for too many objects at once, especially if those objects are different types. If we have a large number of Buttons (5 for example) and we want all button objects to be handled differently when clicked, we cannot easily do that without forcing our onClick() method implementation to check to see which button was clicked and adding separate logic for each button in the same method. Using anonymous inner classes is much cleaner in this situation, especially since we don't have too many objects to worry about.
4.2.3 Run SimpleJokeList Tests
Primary Authors: James Reed and Kennedy Owen
Adviser: Dr. David Janzen