In this chapter, we are going to take a look at how to provide content within your application. We'll cover how to share that content, and how to access and modify the data that represents that content.
We have gotten significantly more advanced as we have progressed from chapter to chapter, and this chapter is no different. Data access is significantly more complex than event handling and UI design. This is because it involves database design and requesting security permissions for database access. In fact, starting with this chapter, we will need to modify the application's AndroidManifest.xml file, so be warned that we are getting into some fairly complicated concepts and code here.
We'll begin with an overview of exactly what Android content providers are, and what they do for your Android user. After that, you will learn how to use SQLite-based content providers for your Android applications although this is beyond the scope of this chapter and book.
Content provider is a term unique to Android development that means nothing more than a datastore of data values, usually in the form of a SQLite database that is already part of the Android operating system (OS). You can also create your own content providers for your application.
An Android content provider provides you with access to sharable data structures commonly called databases. The basic procedure is as follows:
Get permission to open the database.
Query the data.
Access the data.
In accessing data, you might read the data, write the data (i.e. change the values of the existing data), or append new data onto the database structure, based on the type and level of security permissions that have been established in the AndroidManifest.xml file.
Data can be in Android internal memory or in external memory such as an SD card, or even on an external server that is remote to the Android device itself.
The usual way for content providers to provide data structures for Android applications is via a database management system (DBMS). A DBMS manages a database by providing ways for users to create databases, as well as to populate them with data via reading and writing operations.
There is a complete open source DBMS right inside the Android OS called SQLite. This is a relational DBMS (RDBMS). An RDBMS is based on relationships that can be drawn between data arranged in tables. Later in this chapter, you will see how to write data into these tables in the RDBMS.
The SQL in SQLite stands for Structured Query Language. The "Lite" or "Light" part delineates that this is a "lightweight" version of the DBMS, intended for embedded use in consumer electronics devices, and not a full blown version of SQL, as would be used on a computer system. Later, we will look briefly at how it allows you to access database data records and the data contained within their individual data fields. All you really need to know about SQLite is that it is a part of Android and that you can use it for data storage. Android takes care of the DBMS functions for you!
In a DBMS, the highest level of data storage is the database itself, which contains tables of data in rows and columns. Each table is two-dimensional, where a row is called a record. Within each record are fields, organized into columns, which contain the individual data items that make up the records. Fields can contain different data types, such as numbers, text, or even references to data that is stored somewhere else. However, each field must contain the same data type as the other fields in the same column (see Figure 10-1).
Note that there can be more than one table in a database (and usually is, for both performance and organizational reasons). As long as there is a key (a unique index) for each record in each table, information for a single data entry can span more than one table. For instance, if your key or ID is 217, your personal information and phone information can be in two different tables stored under that same key value.
After the record structure and data fields that define this record structure are set up, don't change the structure later. This is because the currently loaded records and fields may not fit into the new data structure definition correctly. So, it's best to design what your database structure will be up-front, making the DBMS design process especially critical to the success of the project.
The content providers that are provided with the Android OS all use SQLite, because it is compact and open source, so we are going to focus on those in this chapter.
A significant number of SQLite database structures are hard-coded into Android in order to handle things that users expect from their phones and tablets, such as contact address books, camera picture storage, digital video storage, music libraries, and so forth. The most extensive of these SQLite database structures is the Contacts database.
The base-level interfaces of the android.provider
package allow us to access those data structures that define the setup and personalization of each user's smartphone. Obviously, the data in each of these structures will be completely different for each user's phone.
Table 10-1 lists the Contacts database interfaces found on the Android Developer site (http://developer.android.com/reference/android/provider/package-summary.html
).
Table 10.1. The Contacts Interfaces for Android 1.x Support
Interface | Contents |
---|---|
| Organization |
| Groups |
| People |
| Phone numbers |
| Contact photographs |
| IM presences |
| Phone settings |
| Contact methods |
| Phone extensions |
If you browse the Android documentation, you'll see that the interfaces listed in Table 10-1 are all described as "deprecated." Deprecated means that these classes have been replaced by other classes in a newer version of the programming language (such as Java) or API (such as Android). The newer classes that replace older classes are usually more robust or complex, or sometimes they differ only in how they are implemented.
This is what has happened with the Contacts interfaces between Android versions 1.x (1.0, 1.1, 1.5, and 1.6) and Android versions 2.x and 3.x (2.0, 2.1, 2.2, 2.3, and 3.0). So, the database interfaces that work with Android 1.x phones are different than the ones that work on the Android 2.x phones (more advanced or feature-rich database structures, in this case).
If you are going to support 1.5 and 1.6 phones (as we are doing throughout this book), you will need to use the database interfaces listed in Table 10-1.
The good news is that deprecated does not mean disabled. It more accurately means in this case, "not suggested for general use unless you need to support pre-2.0 versions for your Android users." So, if you need to support Android 1.5 and later phones, you can use the interfaces listed in Table 10-1, and they will still work well on 2.x (and 3.x) smartphones. However, you may not be able to access data from a few new fields or tablesunless you add support for the new 2.x DBMS structures in your code by detecting what OS the user is using, and have code sections that deal with each (1.x and 2.x) structure differently.
If you want to be able to access every new feature, you can have your code detect which version of the OS the phone is using, and have custom code that delivers the optimal functionality for each version.
In the case of Android, deprecation (a common problem that developers need to get used to) equates to different versions of the Android OS being able to do different things, and thus having different sets of functionality that can be used for each operating system level or version. With Android this is especially prevalent as different OS versions support different hardware features for new phones and tablets, requiring new APIs and changes to existing APIs in ode to support the new hardware features.
Over time, versional functionality gets more and more difficult to keep track of. Indeed, we already have eight (if you count Android 3.0) different OS versions that our code must work across. Keeping track of all the programming constructs and logic mazes is enough of a challenge for most, without a layer on top of that regarding remembering which constructs and interfaces work or do not work with a given OS version. This is one reason why programmers are so well paid.
Table 10-2 lists some of the new version 2.x content providers for manipulating contact information. Some of these replace the deprecated versions that are listed in Table 10-1, and are available from the same Android developer site link: (http://developer.android.com/reference/android/provider/package-summary.html
).
Table 10.2. Android 2.x Content Providers
Interface | Contents |
---|---|
| For subclassing databases |
| Contact main information |
| Contact options |
| Contact status |
| Phone numbers |
| Group definitions |
| IM presences |
| Account settings |
| IM visibility |
The other collections of content providers that are important within the Android OS are the MediaStore
content providers. These are listed in Table 10-3.
Table 10.3. AndroidMediaStore Content Providers
Interface | Contents |
---|---|
| Album information |
| Artist information |
| Audio information |
| Audio genre information |
| Audio playlist information |
| Digital images |
| Digital video |
| Generic media store |
In the rest of this chapter, we will look at how to declare content providers for use, access them, read them, modify them, and append to them.
Before a content provider can be used, it must be registered for use by your Android application. This is done by using some XML markup in the AndroidManifest.xml file. The <provider>
tag, so aptly named, allows us to define which content providers we are going to access once our application is launched. Here's a <provider>
tag for the Images
content provider:
<provider android:name="MediaStore.Images.ImageColumns" />
All Android content providers expose to developers a publicly accessible unique reference, or address, if you will, to each database. This address is called a URI, and the Android constant that points to the datalocation within the database tableis always called CONTENT_URI
.
A content provider that provides access to multiple tables will expose a unique URI for each table. Here are a couple examples of predetermined Android URI constants:
android.provider.Contacts.Phones.CONTENT_URI android.provider.Contacts.Photos.CONTENT_URI
The first reads "android
(the OS) dot provider
(the component type) dot Contacts
(the database) dot Phones
(the table) dot CONTENT_URI
(the constant that points to the data location)." Yes, there is a logical method to the madness here.
URI objects are used for much more than just Android content providers, as you have seen in Chapter 8. All of the ones that are used to access Android content providers start with content://
, just like a web address starts with http://
.
Let's set up our Chapter10 project folder in Eclipse right now, so you can learn a little more about the Android manifest editor and how Eclipse can automate the Android manifest XML coding process for us.
If you still have the Chapter9 project folder open from the previous chapter, right-click that folder and select Close Project.
Then select File
Fill it out as follows (and shown in Figure 10-2).
The AndroidManifest.xml file is usually referred to as the manifest for your application, and it tells the Android OS what we intend to do with our application. It is accessed during the initial launch of your application to set up the memory for the application and to boot up any system resources or pointers (addresses to things that we are going to talk with or connect to) that are needed for the application to run successfully.
In this case, that means we will be asking Android for permission to access, and possibly even change (depending on the tags we add), one of the Android databases outlined in the previous tables. We need to get permissions to use certain areas of the OS so that Android can implement a robust level of security within its OS infrastructure.
To define permissions, use the <uses-permission>
tag:
<uses-permission android:name="android.permission.READ_CONTACTS" />
This tag allows the application to READ
the CONTACTS
database. Read-only operations are inherently safe, as we are only looking into these databases and reading from them. A read operation is nondestructive to a database.
If we wish to change (overwrite or update, and append) data in a database, we need to use a different permission tag that tells Android that we are going to write data to an Android OS database. In this case, WRITE_CONTACTS
represents the permission and database we will use. As you may have guessed, the WRITE
version of the tag looks like this:
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
Permission for write operations is a bit more serious matter, due to the fact that we are now able to screw up the database. In this case, we are dealing with the smartphone user's contacts data, and we might overwrite data that was there before our app ever accessed it.
There are different permission tags that control different levels of access to services or databases that are part of Android. To see a list of all of them, and to get an idea of what Android will let you access with regard to smartphone hardware, features, and databases, check out this link: developer.android.com/reference/android/Manifest.permission.html
. You will be amazed and empowered.
Now let's see how easy it easy to use Eclipse to add the necessary permissions. Follow these steps:
Right-click the AndroidManifest.xml file in the Project Explorer navigation pane, as shown in Figure 10-3, and select Open
or hit the F3 key on the keyboard.
In the Chapter10 Manifest tab, click the Permissions tab at the bottom of the window (see Figure 10-3).
Click the Add... button in the right pane.
Select the Uses Permission entry at the bottom of the list, and then click OK (see Figure 10-4).
You'll see the uses-permission
tag in the Permissions pane. From the drop-down menu that lists permissions, select android.permission.READ_CONTACTS
(see Figure 10-5). Now it will appear in the left part of the pane.
Selecting the Uses
Permission type on the right should update the pane at the left, but currently it does not, so we (redundantly, since it's already at the bottom of the list) click the Down button to force the manifest editor to update the left pane with the proper uses-permission
tag setting.
Repeat steps 3 through 6 to add another uses-permission
tag, this time selecting the android.permission.WRITE_CONTACTS
option (see Figure 10-6).
That's all there is to adding our read and write permissions. Figure 10-7 shows our AndroidManifest.xml file with the two permission tags at the bottom, before the closing tag:
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission> <uses-permission android:name="android.permission.WRITE_CONTACTS"></uses-permission>
Anytime you are working with the Eclipse manifest editor, you can click the AndroidManifest.xml tab at the bottom of the window and see what this helper is doing as far as writing the actual XML markup code.
Now that we have permissions to read and write to the Contacts database, we can get started working with databases.
Android SQLite uses a table-based database model, where rows represent each data record and the columns represent the data fields, each with a constant type. In this way, each piece of data in each column is the same exact type or classification, and each row is a unique collection of data of these types spanning across the row.
In this example, we are going to work with the Contacts.People
table. After we add some sample data to this table, it will look like Table 10-4.
The column headers are the names that are used by Android to reference the data held in each column. These are what you use in your Java code to access each field of data within each record. For example, in some Java code we will write, we will refer to People.NAME
and People.NUMBER
.
The column names prefaced by an underscore character (_ID
and _COUNT
) are data fields assigned and controlled by Android; that is, you cannot WRITE
these values, but you can READ
them.
Now let's add the four data records shown in Table 10-4 into our Android emulator. (If you like, you can add more than four records.) We'll do this using the utilities that come on the smartphone. Follow these steps:
Run the emulator as usual by choosing Run As
Another way to start the emulator is to select Window
Press the Home button. You will see four icons on the home screen (shown in Figure 10-8 on the left side) labeled Messaging, Dialer, Contacts, and Browser. The one called Contacts is a front-end to our Contacts database and will allow us to add in the records shown in Table 10-4.
Click the Contacts icon to launch the Contacts database, which is initially empty. The screen tells us that we do not yet have any contacts and how to add new contacts to the Contacts database (as shown on the right side of Figure 10-8).
Click the Menu button to bring up a menu from the bottom of the screen (similar to the menu we created in Chapter 7) that offers four different options for working with the Contacts database.
Select the New Contact option to bring up the new contact data-entry form.
Fill out the name (People.NAME
) and mobile phone number (People.NUMBER
) fields at the top of the screen (as shown on the left side of Figure 10-9), and then click the Menu button.
Select the Done option to add the record to the database. Our addition appears on the screen (as shown in the right side of Figure 10-9).
Repeat steps 4 through 7 to add the three other names in Table 10-4, and maybe a few of your own.
Let's get started writing our application that will access the Contacts database. We'll do some data queries against it, update the data, add data, and delete data.
First, let's add a button to our example app's main.xml file that will trigger our database query via an onClick
event (as discussed in Chapter 9 regarding events processing, or, as discussed in the previous chapter).
Right-click the main.xml file, which is located under the /res/layout folder. Then open the Eclipse layout editor and add the button via drag-and-drop as you have done in previous examples. Here is the code that shows the changes to the Button
and TextView
we need to make (see Figure 10-10):
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Click Button Below to Run a Query" /> <Button android:text="Click to Query Contacts Database" android:id="@+id/queryButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" />
Next, right-click the /src/content.providers/DatabaseExamples.java file and select Open
.
First, we will add in our Button
object declaration and our onClick
event handling code, as we did in the previous chapter. Later, we'll write our custom query method, once our UI is in place. To get things going, add the following three import
statements that we need to define our Button
:
import android.widget.Button; import android.view.View; import android.view.View.OnClickListener;
Remember that you use the import
statement to pull in the classes that you are going to leverage in your code.
Now declare our Button
object, like so, with some fairly standard code:
Button queryButton = (Button)findViewById(R.id.queryButton);
Next, use the setOnClickListener()
method to add the ability to handle events for this button, using the following lines of code (see Figure 10-11). First, we attach the new OnClickListener
to our queryButton
, and then inside the onClick
event handler, we assign the queryContactPhoneNumber()
method (which we will code next), to be run when an onClick
event is encountered. Note in Figure 10-11 that queryContactPhoneNumber()
is underlined in Eclipse, which tells us that the method we are calling does not (yet) exist.
queryButton.setOnClickListener(new OnClickListener() { public void onClick(View view) { queryContactPhoneNumber(); } });
As you've seen, when a method does not yet exist, Eclipse puts a red X in the left margin of the code-editing pane and a red underline under the method name. If you want to remove those error indicators immediately, simply hover your cursor (mouse) over the red underline for a second, and select the Create Method option when the list of possible solutions pops up underneath it. Hovering your mouse this way is a great technique for learning more about Java and Eclipse. Don't be afraid to explore and experiment with the Eclipse IDE as you work through this book.
Next, let's add the four new import
statements that we need (shown in Figure 10-12). The first brings in our familiar android.widget.Toast
class to easily display our data via the Toast
UI widget. The second imports the android.net.Uri
class that allows us to define the Uri
object we need to access the database. The third imports the all-important Cursor
class android.database.Cursor
that allows us to traverse the data within all of the Android databases. Finally, android.provider.Contacts.People
is the table we will be accessing:
import android.widget.Toast; import android.net.Uri; import android.database.Cursor; import android.provider.Contacts.People;
Now we can write our queryContactPhoneNumber()
method, to query the database (also shown in Figure 10-12).
private void queryContactPhoneNumber() { String[] cols = new String[] {People.NAME, People.NUMBER}; Uri myContacts = People.CONTENT_URI; Cursor mqCur = managedQuery(myContacts,cols,null,null,null);
if (mqCur.moveToFirst()) { String myname = null; String mynumber = null; do { myname = mqCur.getString(mqCur.getColumnIndex(People.NAME)); mynumber = mqCur.getString(mqCur.getColumnIndex(People.NUMBER)); Toast.makeText(this, myname + " " + mynumber, Toast.LENGTH_SHORT).show(); } while (mqCur.moveToNext()); } }
Let's decipher exactly what is going on in this query method that we have written. Our method is declared private
(meaning it operates completely inside the class that contains it) and void
, as it returns no values. The first line defines a string array variable called cols
and instantiates it with a new string array loaded with the value of two constants from the Contact.People
table called NAME
and NUMBER
. These are the two data fields from which we wish to access data.
private void queryContactPhoneNumber() { String[] cols = new String[] {People.NAME, People.NUMBER};
The next line creates a Uri
object called myContacts
and sets it equal to the People.CONTENT_URI
table address that we are going to query.
Uri myContacts = People.CONTENT_URI;
We then need to create a Cursor
object called mqCur
and assign to it the results of the call to the managedQuery()
method. This method uses the myContactsUri
object, the cols
column references that we are going to pull data from, and three null
s (which represent more complex SQLite operations).
Cursor mqCur = managedQuery(myContacts,cols,null,null,null);
The Cursor
object that is now properly populated with our managedQuery()
results will be used in our iterative code, a do
...while
loop inside an if
statement, to traverse the records of our table that managedQuery()
accesses.
The if
part of the statement is true when the mqCur
object has been positioned at the first record of the results via the moveToFirst()
method. When this happens, the contents of the if
statement are executed.
if (mqCur.moveToFirst()) {
The myname
and mynumber
string variables are cleared by setting them to null
before we enter into the do
...while
loop. The loop is started on the next line with a do
construct containing three logical programming statements. It ends with a while()
condition that says, "Move mqCur
cursor object to the next record."
As long as there is a next record that can be moved to, this will equate to true. When it does not (at the end of the last record in the results), it will equate to false and drop out of the loop, which will cease to function, just as we intended. In other words, as long as there is another record to process, we'll do another run of the code in the loop.
do { ... } while (mqCur.moveToNext());
Now let's look at the three things done in the do
...while
loop while there are records to read.
First, we set the myname
variable to the value of the data that is found in the current record of the results (on the first loop entry, this is the first record; on the second loop entry, this is the second; and so on).
myname = mqCur.getString(mqCur.getColumnIndex(People.NAME));
We do this via two methods of the mqCurCursor
object:
The getColumnIndex()
method gets the internal reference or index number for the People.NAME
column for the current record.
getString()
gets the string data from that location in the results and puts it into the myname
variable.
We repeat the process in the next line of code for mynumber
using People.NUMBER
. It is also held in a string format, so you can use dashes or whatever you like between the numbers.
mynumber = mqCur.getString(mqCur.getColumnIndex(People.NUMBER));
Once our myname
and mynumber
string variables are loaded with the data values from the database record, we call our familiar Toast
widget and display the record on the screen. Notice in this version of the Toast
widget we get a little more advanced than just passing a text string in the second argument of the makeText()
method. Here, we use our two variables (which contain text strings) and concatenate them (attach them) to a " "
space character using the +
operator (used for joining strings together):
Toast.makeText(this, myname + " " + mynumber, Toast.LENGTH_SHORT).show();
Note that this could also be written in two lines of code:
Toast.makeText(this, myname + " " + mynumber, Toast.LENGTH_SHORT); Toast.show();
Now right-click your Chapter10 folder and choose Run As
Now you'll see how to add new content to a content provider database. Here, we will add a new contact name and phone number to the Contacts database.
First, copy and paste the first Button
tag in our main.xml file and change the ID to addContactButton
. The text of the button should read "Click to add a Contact to the Database"
(see Figure 10-14).
<Button android:text="Click to add a Contact to the Database"
android:id="@+id/addContactButton"
android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" />
The first stage of the Java code is to add two global objects that all the methods in our class can use. There are two URIs that will contain the locations where we can add data and change data:
public class DatabaseExamples extends Activity {Uri addUri = null;
Uri changeUri = null;
Next, let's add in the code to implement the second button for our UI by copying the Button
object declaration and the onClick
event handling code and pasting it immediately underneath the existing UI code in our DatabaseExamples
activity class.
Change the Button
variable name to addButton
, and change the R.id
to point to our new addContactButton
. Also, set our method call to the new addContactPhoneNumber()
method we are going to write (see Figure 10-15). Here is the new code:
Button addButton = (Button)findViewById(R.id.addContactButton);
addButton.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
addContactPhoneNumber("Steve Wozniak", "415-555-7654");
}
});
Next, we are going to add the new method addContactPhoneNumber()
.
private void addContactPhoneNumber(String newName, String newPhone) { ContentValues myContact = new ContentValues(); myContact.put(People.NAME, newName); addUri = getContentResolver().insert(People.CONTENT_URI, myContact); Uri contentUri = Uri.withAppendedPath(addUri, People.Phones.CONTENT_DIRECTORY); myContact.clear(); myContact.put(People.Phones.TYPE, People.TYPE_MOBILE); myContact.put(People.NUMBER, newPhone); changeUri = getContentResolver().insert(contentUri, myContact); Toast.makeText(this, "New Contact: " + newName + " " + newPhone, Toast.LENGTH_SHORT); }
We make sure that the addContactPhoneNumber()
private method is declared with the correct parameters, as follows:
private void addContactPhoneNumber(String newName, String newPhone) {
This is a bit different from our queryContactPhoneNumber()
method, as we are passing the method two string parameters: a name and a phone number. Since the addContactPhoneNumber()
method does not return any values, it is still a void
method and is declared as such, just like the others.
Now we are ready to write the code that will add a new name and phone number to the Contacts database. The first thing that we need to do is to create a ContentValues
object called myContact
that defines the table, column, and data values that need to be passed into the content provider. Since this is a new class that we are using in our code, we also need to add a statement to the end of our list of import
statements (see Figure 10-16).
import android.content.ContentValues;
After we do that, we can instantiate a new ContentValues
object called myContact
via the following declaration:
ContentValues myContact = new ContentValues();
Immediately after that, we need to configure that object with a data pair via the put()
method. This loads the ContentValues
object with the table (People
), the column (or field of data to operate on) NAME
, and the string variable with the name in it, newName
.
myContact.put(People.NAME, newName);
Next, we use the getContentResolver()
method to insert the myContactContentValues
object into the People
table, which is at the location specified by CONTENT_URI
constant we discussed earlier in the chapter:
addUri = getContentResolver().insert(People.CONTENT_URI, myContact);
This writes the newName
variable that we loaded into our myContactContentValues
object into the People.NAME
database column that we specified in the same object. So, now our newName
variable passed to our method has been taken care of, and we just need to do the same thing for our newNumber
data variable. Then we will be finished. After this call, addUri
will hold the location of the newly inserted record.
The next line of code declares a Uri
object named contentUri
that appends the People.Phones.CONTENT_DIRECTORY
onto the addUri
and creates a new, more detailed URI object for the next query. (We are basically setting the location of where to add the phone number by using the location of the new name record as a reference.) Now all we need to do is change the data in the myContactContentValues
object for the final data-insertion operation.
Uri contentUri = Uri.withAppendedPath(addUri, People.Phones.CONTENT_DIRECTORY);
The first thing we want to do to the myContact
object is to clear it, or basically turn it into an empty object with a clean slate. Then, in the next two lines, we use the put()
method to load the myContactContentValues
object with the URI and table and column values for the phone number field that we wish to write, and the newPhone
phone number string variable data (415-555-7654), using the following lines of code:
myContact.clear(); myContact.put(People.Phones.TYPE, People.TYPE_MOBILE); myContact.put(People.NUMBER, newPhone);
Finally, we call our powerhouse getContentResolver()
method to go into our content provider and insert the phone number data into the correct table and data column (data field) location. This is done with the following code:
changeUri = getContentResolver().insert(contentUri, myContact);
Once our data record is written by the two getContentResolver()
operations, we can send a Toast
to our users in the usual way, telling them that the write has been performed.
Toast.makeText(this, "New Contact: " + newName + " " + newPhone, Toast.LENGTH_SHORT);
Figure 10-16 shows the code as it appears in Eclipse with the import
statement, two global Uri
object variables declared, and our addContactPhoneNumber()
method highlighted.
We declared the two addUri
and changeUri
URI objects at the top of our code outside all of our methods so that they can be used in any of the methods in this class. We will be using them in other methods later in this chapter, so we've made them available for that purpose.
Now right-click your Chapter10 project folder and select Run As
To see the new data for Steve Wozniak, select the Contacts icon, hit the Menu button at the bottom of the screen (on the phone), and choose the Search function. Then scroll down the list until you see the Steve Wozniak entry (highlighted on the right in Figure 10-18).
Now that you've seen how to add data to a contact provider, let's look at how to modify the content provider's data.
Changing an existing record is another write operation as far as a database is concerned, because new data is written to a database record field, and that data overwrites the existing data that was there.
Let's dive right into our usual work process to see updating content in action.
First, in main.xml, copy the addContactButtonButton
tag and paste it right underneath our other Button
tags. Change the ID
attribute to modifyPhoneButton
. This reflects the fact that we are going to modify the phone number to the new phone number, just as we would do in real life (people don't change names quite as often as they change mobile phone numbers).
Next, change the text of the button to read "Click to Modify the Contact in the Database"
. Here's the code in your Eclipse editor's main.xml tab (also shown in Figure 10-19):
<Button android:text="Click to Modify the Contact in the Database" android:id="@+id/modifyPhoneButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" />
To finish off implementing the UI for this new database operation, let's do a similar cut-and-paste operation in our DatabaseExamples.java file. Add the addButtonButton
object and the addContactPhoneNumber()onClick()
method call, and turn them into a modButtonButton
object and an onClick
event handler that calls a modifyPhoneNumber()
method (see Figure 10-20):
Button modButton = (Button)findViewById(R.id.modifyPhoneButton); modButton.setOnClickListener(new OnClickListener() { public void onClick(View v){ modifyPhoneNumber("916-555-1234"); } });
The real heavy lifting is done in our modifyPhoneNumber()
method, which will update the phone number in the database record we just added. It takes a single string containing the new telephone number to replace the existing one (see Figure 10-21).Also notice in Figure 10-21 that we have collapsed our previous two methods using the "+" feature in Eclipse that allows us to expand and contract blocks of code for easier viewing of what we are working on currently. This is shown with a small red square at the left of the screenshot.
private void modifyPhoneNumber(String replacePhone) { if (changeUri == null) { Toast.makeText(this, "You need to create a new contact to update!", Toast.LENGTH_LONG).show(); } else { ContentValues newPhoneNumber = new ContentValues(); newPhoneNumber.put(People.Phones.TYPE, People.TYPE_MOBILE); newPhoneNumber.put(People.NUMBER, replacePhone); getContentResolver().update(changeUri, newPhoneNumber, null,null); Toast.makeText(this, "Updated phone number to: " + replacePhone, Toast.LENGTH_SHORT).show(); } }
The modifyPhoneNumber()
method uses an if...then...else programming loop structure. First, let's make sure there is data in the changeUri
data object by comparing the changeUri
object to null
via the if (changeUri == null)
construct.
if (changeUri == null) { Toast.makeText(this, "You need to create a new contact to update!", Toast.LENGTH_LONG).show();
If this construct equates to true, we print a Toast
message, saying that the add operation has not been done yet, and suggesting that the user use the add method (which we just wrote) to create the record that we want to modify.
If the (changeUri == null)
equates to false, it means that the changeUri
is loaded with the database and column references needed to access and modify the database record. Then we can continue and execute the database modification via four lines of code and a Toast
notification that tells us what was done to the database.
getContentResolver().update(changeUri, newPhoneNumber, null,null); Toast.makeText(this, "Updated phone number to: " + replacePhone, Toast.LENGTH_SHORT).show(); } }
The first line of code is the creation of the newPhoneNumberContentValues
object, which will hold our database names and constants that we will use to reference the phone number field in the Contacts database.
} else { ContentValues newPhoneNumber = new ContentValues();
First, we load the newPhoneNumberContentValues
object with the columns of data we are going to modify. In the second line of code, we state that the People.Phones.TYPE
will be People.TYPE_MOBILE
(that is, we are updating the mobile number). We then use the People.NUMBER
database constant to say we want to update the number with the contents of the replacePhone
data variable that we passed into the modifyPhoneNumber()
call.
newPhoneNumber.put(People.Phones.TYPE, People.TYPE_MOBILE); newPhoneNumber.put(People.NUMBER, replacePhone);
In our fourth line of code inside the else
section of our loop, we call the getContentResolver().update()
method:
getContentResolver().update(changeUri, newPhoneNumber, null,null);
We pass it the following objects:
changeUri
(which we created in the addContactPhoneNumber()
method) specifies the location of the last record we worked with, which is the one we want to update.
newPhoneNumber
is a ContentValues
object that specifies which field of that record structure we wish to modify. It also specifies the updated data for that data field (the new mobile number).
Finally, we add in our Toast.makeText()
call to display the data we have modified once the getContentResolver().update()
is complete.
Toast.makeText(this, "Updated phone number to: " + replacePhone, Toast.LENGTH_SHORT).show();
Compile and run the application in the Android 1.5 emulator, and you will see our new Click to Modify the Contact in the Database button, as shown in Figure 10-22.
We can now query the database, add a record to the database, and change the phone number in an existing database record. Let's complete this tour of common database operations by adding an option to delete a record from the content provider database.
Our final example of manipulating the database demonstrates how to delete database records. We'll also make a few final changes in our main.xml UI code to make everything look a bit more professional.
For the TextView
tag, change the text
attribute to read "Click Buttons Below to Query, Add, Modify, Delete"
. Also add 25 dip of padding to the top and 50 dip of padding to the bottom to space out the objects on the application screen and make it more readable. Here's the new code (also shown in Figure 10-23):
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content"android:text="Click Buttons Below to Query, Add, Modify, Delete"
android:paddingTop="25dip"
android:paddingBottom="50dip"/>
Use your favorite cut-and-paste work process to copy the modify Button
tag that we just created and paste it underneath the other Button
tags. Change the ID to deleteContactButton
and the text to read "Click to Delete the Contact in the Database"
. Your code should look like this (also shown in Figure 10-23):
<Button android:text="Click to Delete the Contact in the Database" android:id="@+id/deleteContactButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" />
Now we will repeat the same copy-and-paste operation in our Java code. Copy the modButtonButton
object creation and setOnClickListener()
event handling routine. Change the object name to delButton
and the method call to deleteContactPhoneNumber()
(Figure 10-24 shows what your Java code for your UI definitions should look like in the Eclipse DatabaseExamples.java tab).
Button delButton = (Button)findViewById(R.id.deleteContactButton); delButton.setOnClickListener(new OnClickListener() { public void onClick(View v){ deleteContactPhoneNumber(); } });
Now we will create our new deleteContactPhoneNumber()
database method. All we need to do is add in the code that makes sure our changeUri
object is still intact and loaded with reference parameters, and then access our ContentResolver
object to delete the data record. Here's the code (see Figure 10-25):
private void deleteContactPhoneNumber() { if (changeUri == null) { Toast.makeText(this, "You need to create a new contact to delete!", Toast.LENGTH_LONG).show();
} else { getContentResolver().delete(addUri, null, null); Toast.makeText(this, "Deleted contact at: " + addUri.toString(), Toast.LENGTH_SHORT).show(); addUri = null; changeUri = null; } }
This code uses an if
loop that is identical to the one we constructed in the modifyPhoneNumber()
method earlier.
if (changeUri == null) { Toast.makeText(this, "You need to create a new contact to delete!", Toast.LENGTH_LONG).show();
The if
statement basically says. "I our changeUri
object is not loaded with the data from the add and modify operations, then tell the users that they need to create a new contact first to delete the record; otherwise, perform the following operations."
The meat of our code to delete the database record that we have created is inside the else
portion of our if...then...else loop structure. It begins with a call to setContentResolver().delete()
to delete the data record whose particulars are referenced in the addUri
that we created in the addContactPhoneNumber()
routine we coded earlier.
} else { getContentResolver().delete(addUri, null, null);
In this method, we are referencing the addUri
, which references the Contacts database and contact name, rather than the changeUri
, which references the phone number and type data fields. This is because we are deleting the top-level database record, and not just the phone number inside it.
Once we have deleted our database record via the ContentResolver
, we can Toast
to our users a message that it has been deleted. Finally, we set the URIs to null
because we have deleted the record.
Toast.makeText(this, "Deleted contact at: " + addUri.toString(), Toast.LENGTH_SHORT).show(); addUri = null; changeUri = null; }
Now let's select Run As
We now have our finished database-access application. This version is spaced out much better on the screen, and it has all four database operations in place and functioning:
The Click to Query Contacts Database button shows the entire Contacts database, including the new addition.
The Click to Add a Contact to the Database button adds a new record.
The Click to Modify the Contact in the Database button changes the database record to include a new phone number.
The Click to Delete the Contact in the Database button deletes the new record.
We have successfully written code to manipulate one of Android's internal content provider databases.
This is probably one of the most complicated chapters in this book, because it combines the following:
Knowledge of SQLite database design, functionality, and access—in itself a topic that can easily span several books
The Android concept of content providers
The Java programming language constructs that are necessary to access and manipulate these database structures
You should feel a great sense of accomplishment from getting through all of this unscathed. You are learning how Android deals with advanced database concepts and structures.
Most of the content providers that you will be working with in Android are already a part of the OS. They provide access to the common functions that users want in their phones, including contacts, music (audio), entertainment (video), and similar. The built-in content providers were listed in this chapter. We also covered the concept of deprecation, because as of Android 2.x, the internal content provider database structures were enhanced, making pre-2.0 OS tables deprecated, although still usable, as you saw in this chapter.
The primary Java classes in Android that handle content providers are (surprise!) the ContentProvider
class, the ContentResolver
class, and the ContentValues
class. Each plays a critical role in defining (ContentProvider
), accessing (ContentResolver
), and addressing (ContentValues
) a SQLite database structure.
Although there are other ways to pull in data to your Android application, such as off your SD card or off a remote server, the SQLite DBMS is the most robust approach and the only one that can be accessed between applications. Furthermore, this is the most useful content provider type to learn, because all of Android's user data is stored and accessed via these SQLite databases. Unfortunately, it's also the most difficult way to implement content providers (database access) within the Android OS.
44.220.184.63