Chapter 10. Understanding Content Providers

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.

An Overview of Android Content Providers

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:

  1. Get permission to open the database.

  2. Query the data.

  3. 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.

Databases and Database Management Systems

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).

MySQL RDBMS database

Figure 10.1. MySQL RDBMS database

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.

Warning

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.

Android Built-in Content Providers

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.

Contacts Database Contact Providers

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

Contacts.OrganizationColumns

Organization

Contacts.GroupsColumns

Groups

Contacts.PeopleColumns

People

Contacts.PhonesColumns

Phone numbers

Contacts.PhotosColumns

Contact photographs

Contacts.PresenceColumns

IM presences

Contacts.SettingsColumns

Phone settings

Contacts.ContactMethodsColumns

Contact methods

Contacts.ExtensionsColumns

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.

Note

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.

Note

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

ContactsContract.CommonDataKinds.CommonColumns

For subclassing databases

ContactsContract.ContactsColumns

Contact main information

ContactsContract.ContactOptionsColumns

Contact options

ContactsContract.ContactStatusColumns

Contact status

ContactsContract.PhoneLookupColumns

Phone numbers

ContactsContract.GroupsColumns

Group definitions

ContactsContract.PresenceColumns

IM presences

ContactsContract.SettingsColumns

Account settings

ContactsContract.StatusColumns

IM visibility

Android MediaStore Content Providers

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

MediaStore.Audio.AlbumColumns

Album information

MediaStore.Audio.ArtistColumns

Artist information

MediaStore.Audio.AudioColumns

Audio information

MediaStore.Audio.GenresColumns

Audio genre information

MediaStore.Audio.PlaylistsColumns

Audio playlist information

MediaStore.Images.ImageColumns

Digital images

MediaStore.Video.VideoColumns

Digital video

MediaStore.MediaColumns

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.

Defining a Content Provider

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.

Note

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://.

Creating the Content Providers Example Project in Eclipse

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.

  1. If you still have the Chapter9 project folder open from the previous chapter, right-click that folder and select Close Project.

  2. Then select File

    Creating the Content Providers Example Project in Eclipse
  3. Fill it out as follows (and shown in Figure 10-2).

    • Project name: Name the project Chapter10.

    • Build Target: Set this to Android 1.5.

    • Application name: Name the application Android Content Providers.

    • Package name: Set this to content.providers.

    • Create Activity: Check this box and name the activity DatabaseExamples.

    • Minimum SDK Version: Enter 3, which matches a minimum SDK version of 3.

Creating the Chapter10 Android project

Figure 10.2. Creating the Chapter10 Android project

Defining Security Permissions

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.

Tip

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:

  1. 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.

    Adding a permission in the Chapter10 manifest using the Eclipse visual editor

    Figure 10.3. Adding a permission in the Chapter10 manifest using the Eclipse visual editor

  2. In the Chapter10 Manifest tab, click the Permissions tab at the bottom of the window (see Figure 10-3).

  3. Click the Add... button in the right pane.

  4. Select the Uses Permission entry at the bottom of the list, and then click OK (see Figure 10-4).

    Selecting the Uses Permission entry

    Figure 10.4. Selecting the Uses Permission entry

  5. 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.

  6. 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.

    Selecting the READ_CONTACTS permission

    Figure 10.5. Selecting the READ_CONTACTS permission

  7. Repeat steps 3 through 6 to add another uses-permission tag, this time selecting the android.permission.WRITE_CONTACTS option (see Figure 10-6).

    Selecting the WRITE_CONTACTS permission

    Figure 10.6. Selecting the WRITE_CONTACTS permission

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>

Tip

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.

The XML output for the permission additions we made in the visual editor

Figure 10.7. The XML output for the permission additions we made in the visual editor

Now that we have permissions to read and write to the Contacts database, we can get started working with databases.

Adding Data to the Contacts Database

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.

Table 10.4. Contacts.People Database Table with Sample Data

_ID

_COUNT

NAME

NUMBER

44

4

Bill Gates

212 555 1234

13

4

Steven Jobs

425 555 6677

53

4

Larry Ellison

201 555 4433

27

4

Mark Zuckerburg

213-555-4567

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:

  1. Run the emulator as usual by choosing Run As

    Contacts.People Database Table with Sample Data

    Note

    Another way to start the emulator is to select Window

    Contacts.People Database Table with Sample Data
  2. 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.

    Adding new contacts to the Android Contacts database via the Contacts utility

    Figure 10.8. Adding new contacts to the Android Contacts database via the Contacts utility

  3. 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).

  4. 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.

  5. Select the New Contact option to bring up the new contact data-entry form.

  6. 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.

  7. 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).

    Adding a record to the Contact database

    Figure 10.9. Adding a record to the Contact database

  8. Repeat steps 4 through 7 to add the three other names in Table 10-4, and maybe a few of your own.

Working with a Database

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.

Querying a Content Provider: Accessing the Content

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).

  1. 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" />
    Adding our Button code to main.xml

    Figure 10.10. Adding our Button code to main.xml

  2. Next, right-click the /src/content.providers/DatabaseExamples.java file and select Open.

  3. 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;

    Note

    Remember that you use the import statement to pull in the classes that you are going to leverage in your code.

  4. Now declare our Button object, like so, with some fairly standard code:

    Button queryButton = (Button)findViewById(R.id.queryButton);
  5. 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();
            }
    });
    Declaring our import statements and query button in DatabaseExamples.java

    Figure 10.11. Declaring our import statements and query button in DatabaseExamples.java

    Tip

    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.

  6. 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;
    Java for our queryContacttPhoneNumber() and import statements

    Figure 10.12. Java for our queryContacttPhoneNumber() and import statements

  7. 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 nulls (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

Java for our queryContacttPhoneNumber() and import statements
Running a query in the Android 1.5 emulator

Figure 10.13. Running a query in the Android 1.5 emulator

Appending to a Content Provider: Adding New Content

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.

  1. 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" />
    Adding our second button in main.xml

    Figure 10.14. Adding our second button in main.xml

  2. 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;
  3. 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.

  4. 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");
                }
            });

    Note

    This line of code calls our addContactPhoneNumber() method and passes it new database record data so that a new contact entry can be added to the Contacts database.

    Adding the Java code to add in the second button

    Figure 10.15. Adding the Java code to add in the second button

  5. 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.

Writing the Java code for our AddContactPhoneNumber method

Figure 10.16. Writing the Java code for our AddContactPhoneNumber method

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

Writing the Java code for our AddContactPhoneNumber method
Adding a contact in the Android 1.5 emulator

Figure 10.17. Adding a contact in the Android 1.5 emulator

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).

Using the Contacts editor utility in the Android 1.5 emulator

Figure 10.18. Using the Contacts editor utility in the Android 1.5 emulator

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.

Modifying Content Provider Data: Updating the Content

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.

  1. 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).

  2. 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" />
    Adding a modify contact button in main.xml

    Figure 10.19. Adding a modify contact button in main.xml

  3. 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");
                }
            });
    Adding the Java code to implement our modify contact button

    Figure 10.20. Adding the Java code to implement our modify contact button

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();
    }
}
Writing our modifyPhoneNumber() method

Figure 10.21. Writing our modifyPhoneNumber() method

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.

Modifying a contact in the Android 1.5 emulator

Figure 10.22. Modifying a contact in the Android 1.5 emulator

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.

Removing Content Provider Data: Deleting Content

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.

  1. 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"/>
  2. 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" />
    Adding the delete button XML markup to main.xml

    Figure 10.23. Adding the delete button XML markup to main.xml

  3. 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();
                }
            });

    Note

    Since we are simply deleting the record, we do not need to pass the method a variable. This is because the road map to the database information that we wish to operate on is already in our changeUri object, ready to reference.

    Adding the our Java code to implement the delete button

    Figure 10.24. Adding the our Java code to implement the delete button

  4. 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;
            }
        }
    Adding our deleteContactPhoneNumber method to DatabaseExamples.java

    Figure 10.25. Adding our deleteContactPhoneNumber method to DatabaseExamples.java

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

Adding our deleteContactPhoneNumber method to DatabaseExamples.java
Running our final database application in the Android 1.5 emulator

Figure 10.26. Running our final database application in the Android 1.5 emulator

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.

Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
44.220.184.63