Go to http://sites.google.com/site/androidappcoursev3/ for the most up-to-date versions of these labs.
Intro
For this lab you will be developing a new Application named AppRater that suggests other Applications for users to download and try. The purpose of the application is to share fun and interesting applications with other users. The users can then rate the applications. Its a simple application that will be extended and used to run the App Development Contest at the end of the quarter.
Objectives
At the end of this lab you will be expected to know:
Activities
For this lab you will be working with a brand new application, completely independent of the previous labs. Once you have a general understanding of the components of the application you will begin implementing it. You will be given more general instructions in some sections. We think you should be comfortable enough with Android by now that you can figure more out on your own.
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. These are given to you because there are unit tests included in this project as well that depend on these items being declared exactly as they are. These units test 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. In fact, you are encouraged to run these tests to ensure that your application is functioning properly.
1. Setting Up...
1.1 Creating the Project
To begin, you will need to download and extract the skeleton project for the AppRater application.
Extract the project, making sure to preserve the directory structure.
Take note of the path to the root folder of the skeleton project.
Next you will need to setup an Android project for this app. Since the skeleton project was created in Eclipse, the easiest thing is to import this project into Eclipse
1.2 Familiarize Yourself with the Project
The project contains seven java files and two XML layout files. AppRater.java will contain the definition for the main AppRater Activity class. This is the class that will display the list of applications the user is supposed to test and rate. The AppRater class makes use of a very simple XML layout file called app_list.xml which has been completed for you. It is composed of a ListView that displays a list of applications that a user is supposed to try out.
Applications that users are supposed to try out and rate are represented by the App model class, defined in App.java. This is a simple class that encapsulates the name of the application, a rating for it, the Market-URI from which the app can be downloaded (we'll discuss this later but think of it as a link to install it from the Market application), a boolean used to flag the application as installed, and a unique integer ID.
The AppView class is a custom class that is used for visualizing the state of an App object. The AppView class has an App member variable on which it bases the state of its display. It's layout is defined in the app_view.xml XML layout file, which has already been completed for you. This is a very simple layout file, composed of a LinearLayout containing a single TextView. The TextView merely displays the name of the App object that the AppView is visualizing.
The AppView class has three different states:
The AppRater Activity gets the Apps it will display from the AppContentProvider class as a Cursor of Apps. This is quite similar to how the JokeList Activity got a Cursor of Jokes from the JokeDBAdapter. The AppRater Activity's ListView then uses the AppCursorAdapter class to bind AppViews to the cursor of Apps.
The AppRater Activity starts the AppDownloadService in order to add new Apps to the underlying database. The new Apps are retrieved from a web site. This is kind of like launching a new Activity except that there is no User Interface for the Service. The AppDownloadService then downloads and inserts new Apps into the database. In order to insert new Apps into the database the AppDownloadService must use the AppContentProvider's insert method. This is quite similar to how the JokeList Activity (in previous labs) downloads and inserts new Jokes by using JokeDBAdapter.
Once the AppDownloadService has successfully added a new App, it broadcasts a special Intent. The AppRater Activity will use its internal NewAppReceiver class to listen for that special Intent. When it hears the Intent it knows that it has to update its cursor of Apps.
Lastly, once a user has downloaded, installed, and tested an App they can give it a rating. The user can apply a rating to an App via a ContextMenu in the AppRater Activity. The AppRater Activity then saves the changes to the App through the AppContentProvider.
A general class diagram for the project is depicted below. Classes you will be implementing are colored yellow, the new Android classes that you will be working with are colored in blue, and the standard Android classes you should be familiar with are colored in white.
2. AppRater Activity
In this section of the lab you will be learning how to use ContentProviders to retrieve and update persistant data, how to use Services to perform work in the background, and how to respond to system-wide notifications by using BroadcastReceivers. All of the work done in the following subsections should be performed in the AppRater.java file. The file has been setup to import the already completed versions of the AppContentProvider and AppDownloadService classes.
2.1 Querying a ContentProvider
You can interact with a ContentProvider in much the same way that you interacted with the JokeDBAdapter. You can execute a query which will return a Cursor, you can delete data, you can update data, and you can insert data. The main difference is that ContentProviders offer a way to share data between applications. Where your previous Joke database was only visible to the JokeList application, the AppContentProvider that you will implement later on will make your database usable by any Application.
ContentProviders also abstract away how the underlying data is persisted. While in this application you will be using an SQLiteDatabase, you could just as easily store the data in files, on a remote server, or whatever else you can come up with. For a complete background on ContentProviders see the Android Developer Guide on ContentProviders.
For this section you must Query the AppContentProvider for a cursor of Apps that the AppRater Activity will display. When a user clicks on one of the listed apps, the Market application will be launched to display the App's info page. The AppContentProvider you are using is currently pre-populated with some apps so that you can test your implementation.
Hint: use a managedQuery when you query the AppContentProvider.
2.1.1 Fill in onCreate(...)
Start by initializing the AppRater Activity. You should query the AppContentProvider to retrieve a Cursor containing all of the Apps it contains. Use this cursor to initialize your AppCursorAdapter and ListView members.
You should be able to run your application and view a list of Apps.
2.1.2 Launch the Market Activity
When a user clicks on an AppView, you should start the Market Activity implicitly, meaning that you should not use an Intent that explicitly launches the Market Application. Instead, you should use an Intent object that should cause the Market application to be launched. You do this by specifying an Intent action-string and the data to be acted upon. When the Market application starts, it immediately displays the info page for the App that was clicked. Note that the Market app is not included in the emulator. You will need to attach a phone to test this portion of your app. You should handle the exception that gets thrown if you are unable to launch the Market for an App - post a Toast message "Unable to get App from Market" when this happens.
If you are confused on how to implicitly start an Activity or for more details on Intents, see either the Android Documentation or the Android Developer Guide on Intents:
You should be able to run your application, click on an App, and have the Market application display the selected App's info page.
Hint: Consider implementing onItemClickListener in AppRater to handle clicks on your ListView.
2.2 Starting & Stopping Services
Services are application components that run in the background and do not have User Interfaces. Any time that there is some type of code that needs to be run regularly but does not need a user interface you could probably implement it as a Service. Services can be started from your application's Activities that are currently visible, or they can be awoken by System Notifications when your Activities are all closed. The AppRater application will use a Service to update its ContentProvider with new data. For a more detailed description, read the Android Developer Guide on Service Application Components.
The AppContentProvider is currently only storing a single App. In this next section you will have the AppRater Activity start the AppDownloadService class. The AppDownloadService will download more Apps from a server and add them to the AppContentProvider. Most importantly, the AppDownloadService will execute in a background thread to keep the main AppRater Activity responsive. The AppDownloadService will be controlled through the AppRater Activity's Options Menu.
2.2.1 Create the Options Menu
The AppDownloadService needs to be started and stopped from the AppRater's Options Menu. Additionally, you may want to remove all the Apps from the ContentProvider in order to repeatedly test the AppDownloadService, so you will need to create a MenuItem for this as well. Implement the AppRater Activity's Options Menu as follows:
Run your application and you should see the pre-populated Apps. Click the "Start Downloading" MenuItem, and wait a few seconds for the Apps to download and appear in your ListView. You should see a notification in the Notification Bar. Click the "Remove All" MenuItem and all the Apps should disappear.
It is important to note that your AppDownloadService will continue to run. If you wait a few more seconds the ListView should repopulate itself with the Apps. The AppDownloadService will run even after you have closed the AppRater Activity. You must explicitly stop it by using the "Stop Downloading" MenuItem in order to shut it down.
2.3 Responding to Broadcasts
Intents are used as a System-level message passing system. They can be used to start application components, or they can be used to send messages between components. In order to listen for a message, you need to use a Broadcast Receiver.
In the AppRater Application, the AppDownloadService will broadcast an Intent containing the AppDownloadService.NEW_APP_TO_REVIEW action string constant after it successfully downloads and saves a new App to the database. Since the AppDownloadService runs in the background as a Service, this could happen whether the AppRater Activity is running or not. If the AppRater Activity is running, it should display a Toast notification telling the user that a new App was downloaded.
In this section, you will implement the AppReceiver class, which extends BroadcastReceiver. AppReceiver will listen for AppDownloadService.NEW_APP_TO_REVIEW Intents and display a Toast notification when it hears them.
2.3.1 Implement the AppRater.AppReceiver Class
Fill in the AppReceiver.onReceive(...) method so that the AppRater Activity displays a Toast notification telling the user that a new app was downloaded:
2.3.1 Register & Un-Register for Broadcasts
In order for your AppReceiver to be notified when AppDownloadService.NEW_APP_TO_REVIEW action Intents are broadcast, you must register an instance of it with your Activity. Likewise, when the Activity moves out of the foreground, you need to unregister the AppReciever.
Run your application and click the "Remove All Apps" MenuItem to clear the AppContentProvider. Then click the "Start Downloading" MenuItem. After a few seconds the AppRaterActivity should display the new app Toast notification as new Apps are added to the ListView.
2.4 Updating Data in ContentProviders
It is sometimes necessary to modify the data contained in a ContentProvider. In this next section you will add functionality to persist changes to App objects in the ContentProvider. In particular, you will enable the user to edit whether they have installed an App, as well as apply a rating to it. Both of these actions should be performed through a ContextMenu that gets generated by Long-Clicking on an AppView. The main use case is pictured below.
2.4.1 Create the ContextMenu
The Context menu should use a CheckBox MenuItem for selecting whether the App has been installed, and a group of four RadioButtons for selecting the rating to apply to an App.
Implementation Requirements:
Functional Requirements:
Hint: If you have problems with your app not responding to a long-click, check to see that you implemented onItemClickListener in AppRater, rather than an onClickListener on AppView for handling normal clicks.
2.4.2 Persist App Changes
The tricky part here is figuring out which App to make the changes to. You need to identify the AppView that was clicked when the ContextMenu was generated. From the AppView you can retrieve the App. Follow these instructions to retrieve the AppView and its App:
Here are the Functional/Implementation Requirements you should statisfy:
An additional use case for uninstalling a previously installed and rated App is pictured in the screenshots below. Notice that the rating should be reset to App.UNRATED if it is re-installed.
Hints:
3. Implementing Background Services
3.1 AppDownloadService
For this section of the Lab you will implement your own version of the AppDownloadService that you used in the AppRater Activity. For a complete Background on the Service class you should probably read the Android Developer Guide on the Service Lifecycle and the Android Documentation on the Service Class.
3.1.1 Handle Initialization and Destruction of the Service
When the Service is first started, the onCreate() method is called. You should initialize your Timer and TimerTask.
When the Service is stopped, its onDestroy() method is called. You should free up any resources that you have allocated in this method, including threads.
3.1.2 Fill In the getAppsFromServer() Method
This method should download a list of Application Name and InstallURI pairs from a server. For each Application Name and InstallURI pair, the method should construct an App object and hand it over to the AppDownloadService.addNewApp(...) method.
app-name-1,market://details?id=com.package1.app1;app-name-2,market://details?id=com.package2.app2;app-name-3,market://details?id=com.package3.app3;
3.1.3 Fill in the addNewApp(...) Method
This method should first test to see if the App already exists in the ContentProvider. You should query the AppContentProvider. If the App does not exist, then you should insert it into the ContentProvider and call the AppDownloadService.announceNewApp() method.
3.1.4 Fill in the announceNewApp() Method
This method should broadcast an Intent containing the AppDownloadService.NEW_APP_TO_REVIEW action string, and launch a Notification.
3.1.5 Update the AppRater Activity
The AppRater Activity class is currently using the AppDownloadService implementation that was provided to you. You need to remove the import statement at the top of the AppRater.java file. Delete the following line:
import edu.calpoly.android.appraterkey.AppDownloadService;
You should now test your application to ensure that it functions as it did before.
3.1.6 Update Your Manifest
You will need to add your Service to the AndroidManifest.xml file. You can look in the file to see how it was done for the appraterkey.AppDownloadService.
4. Implementing a ContentProvider
4.1
In this final section of the lab, you will implement your own version of the AppContentProvider you have been working with. The AppContentProvider uses an SQLiteDatabase to persist the App data. Much of the work you will do to implement this class will be very similar to the work that you performed for the JokeDBAdapter class. The main difference being that there are only four basic methods, as you have seen each of them so far. One method each for querying, inserting, updating, and deleting data from the SQLiteDatabse.
4.1.1 Implement AppDBHelper
You should begin by implementing the AppDBHelper class so that you can use it for opening the SQLiteDatabase. The Database creation and deletion strings are stored as constants of the AppDBHelper class.
4.1.2 Initialize the ContentProvider
When ContentProvider is being started, the onCreate(...) method gets called. You should initialize your SQLiteDatabase member variable here. Read the Android Documentation on the ContentProvider.onCreate(...) to make sure that you are returning the proper value.
4.1.3 Implement the query(...) Method
You should start by reading the Android Developer Guide on Summary URI Structure before continuing on. This method should execute one of two different queries on its SQLiteDatabase member variable depending on the Uri argument that was passed in:
Tips:
4.1.4 Implement the Remaining ContentProvider Methods
You should continue on to implement the insert(...), update(...), and delete(...) methods. Each of these methods should check the Uri parameter just like the query(...) method did and either insert/update/delete all matching records or a single record. Use the tips you were given in the previous section.
If you're at all confused about what the method should do, what the method should return, or what the parameters are for, read the Android Documentation on the ContentProvider Class.
4.1.7 Use Your AppContentProvider
The AppRater class and AppDownloadService class are currently using the AppContentProvider implementation that was provided to you. You need to remove the import statements at the top of the AppRater.java and AppDownloadService.java files in order to use your implementation. Delete the following line from each of these files:
import edu.calpoly.android.appraterkey.AppContentProvider;
You should now test your application to ensure that it functions as it did before.
4.1.8 Update Your Manifest
You will need to add your ContentProvider to the AndroidManifest.xml file. You can look in the file to see how it was done for the appraterkey.AppContentProvider.
5. Deliverables
To complete this lab, you will be required to:
Primary Author: James Reed
Adviser: Dr. David Janzen