Chapter 8

Complex Device-Based Data: Android Contacts

WHAT’S IN THIS CHAPTER?

  • Using the Android Contacts database
  • Using the ContactsContract API
  • Learning how the Contacts database illustrates content provider concepts

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

Please note that all the code examples in this chapter are available at https://github.com/wileyenterpriseandroid/Examples.git and as a part of the book’s code download at www.wrox.com on the Download Code tab.

Most of this book is about innovative techniques that simplify your use of databases in Android and in network services backing Android applications. However, this chapter looks at a complex multi-table database with a complex content provider API that has a conventional design in the context of the database APIs in the Android system. That is, this chapter shows an example of how to create a big, complex SQL database and provider interface as a contrast to using the kinds of techniques covered in the rest of this book.


NOTE To get the most out of this chapter, you should have read Chapter 2, on relational database concepts and SQL, and Chapter 4, on the ContentProvider class and related classes.

PIM DATABASES: FOSSILS FROM SMARTPHONE PRE-HISTORY

The Contacts database was influenced by earlier smartphone architectures. The first smartphones were devices that combined mobile phones with personal information managers (PIMs). These pre-iPhone smartphones dated back to an era when there was no wide-area data connectivity, cloud services didn’t exist, and syncing meant syncing to data on your PC. These smartphones enabled you to carry your PIM data in your phone.

Android’s designers might have done most of this “in the cloud,” but that would have made Android less open, and more closely tied to Google’s cloud services. And there are still places where your contacts and other PIM data are useful without data connectivity. If the goal is to provide flexible, unified contact information using the capabilities available on a mobile device, the Android Contacts provider is a good example.

ANDROID’S CONTACTS PROVIDER

The Contacts database API, called ContactsContract, isn’t a “normal” Java API. It isn’t a set of methods contained in a ContentProvider subclass. Instead, it consists mostly of constants — strings, values, and static objects — that help you access information using the URL-based API style of a content provider.

When you get contact information, you get Cursor objects, and you get them through a loader or, more directly, through a content resolver. There is no object-relational mapping (ORM) in the ContactsContract API, and there are no objects that model individual contacts. The API and the Cursor objects put as little as possible between you, the contacts data, and the UI widgets. If you look at the example code listed later in this chapter, you can see where the program gets a Cursor object where the data from a table will be loaded into a view.

When databases get more complex in Android, the APIs to access them provide access to the tables in the database, of course, but they also provide access to “abstract” tables, which are the results of SQL queries that select and combine information across the tables in the database. This is how an Android content provider-style API abstracts queries from users of the API. You will explore this provider in two ways and will see which tables are “real” and which are created on the fly.

THE CONTACTSCONTRACT API

The ContactsContract API consists mainly of constants. For example, the constant AUTHORITY has the value com.android.contacts, which is the string you need to form a URL to access the Contacts provider. To take another example, the tables in the Contacts provider are represented by embedded subclasses, and you access those tables using the CONTENT_URI static object. Commonality in constants is created using interfaces. By implementing interfaces with no methods, the ContactsContract classes achieve a kind of multiple inheritance of constants and static objects.

You can find the documentation you need to understand this API in two places. First, you need to understand the Contacts provider, which is the part of the Android system that implements the API you’ll use. Documentation on the Contacts provider is found here:

http://developer.android.com/guide/topics/providers/contacts-provider.html

You also need the ContactsContract API documentation, which you can find here:

http://developer.android.com/reference/android/provider/ContactsContract.html

Not only is the Contacts provider a complex database, the API to access it is an example of a complex hierarchy of classes and interfaces. If you are still puzzled about the difference between the Contacts provider and the ContactsContract API, take a look at the documentation on designing and creating content providers:

http://developer.android.com/guide/topics/providers/content-provider-creating.html

What you are looking at in the Contacts provider is a particularly complex and evolved provider.

The API style used in the ContactsContract API is common and idiomatic in Android, but unusual in Java practice, in general. We have been using the term API, but that usually means a collection of classes and methods. Here, you have constants and static objects organized into classes and interfaces. It’s an unusual style of API: It combines Java classes and interfaces with a REST-style system of URI objects to access the information in the provider.

There are benefits and drawbacks to this unusual API style: The benefits come, chiefly, from the way the API style mirrors REST stylistically. Among the drawbacks is the fact that the API style invites probing through reflection, and, if that probing uncovers undocumented parts of the API, the API can be accused of failing to abstract its implementation.

To illustrate these points, we want you to take a look at an app in the next section that explores the Contacts provider via the ContactsContract API.

A CONTACTS PROVIDER EXPLORER

The example application for this chapter explores the data contained in the Contacts provider. It does so by enumerating the classes that represent tables in the API, and by enumerating the columns in each table — that is, it queries the Contacts provider for all the rows in all the tables named in the API and displays all the columns in each row. As noted earlier in the chapter, you can get the source code for this application at https://github.com/wileyenterpriseandroid/Examples.git and as a part of the book’s code download at www.wrox.com.

To follow along with this chapter, you should import the example source code into a project in your Eclipse workspace.

Running the application results in displays like the one shown in Figure 8-1. Be aware that if you have no contacts in the database, there will be no contacts to display.

The Option menu, shown open and on the right in Figure 8-1, enables you to select a table to peruse. The list on the left side of the screen in Figure 8-1 shows a column from the table (in this figure the table selected is an empty one), enabling you to select a row based on the data from that column.

When you select a row, the row information is shown in the Item tab (see Figure 8-2).

The table information is shown in the Table tab (see Figure 8-3).

Code for Exploring a Database

Rather than just run down a list of constants and explain what they mean, it’s better to show how they are used. Using these constants in the context of Android’s content provider and related APIs illustrates more clearly what is inside the Contacts provider.

The goals for this example program are as follows:

  • Enabling you to find every table corresponding to every class that contains a static object named CONTENT_URI, which is to say every table accessible through the ContactsContract API
  • Enabling you to see the structure of the table, including the names of every column and the number of rows in a large, real-world address book
  • Enabling you to see the data in a real address book

In order to see what’s in your address book, compile and run the example, and make sure you see something similar to the figures shown previously in this chapter. Select different tables from the Option menu. Select the Table and the Data tabs to view the information about the table and about the row in the table you selected from the list on the left side of the screen.

Source Code for a Contacts Provider Explorer

The listings that follow are really all part of one long program listing.

Only the PickFragment.java class is included here, because all the important code for exploring the Contacts provider via the ContactsContract API is in this class.

Exploring the Menu of Tables

This application (starting with Listing 8-1) explores an API with unusual characteristics. It consists mostly of constants that have to do with tables that can be queried in a content provider.

LISTING 8-1: Your first look at PickFragment.java

    package com.enterpriseandroidbook.contactscontractexample; 
 
    import java.util.ArrayList; 
    import java.util.Arrays; 
    import java.util.ListIterator; 
 
    import android.app.Activity; 
    import android.app.Fragment; 
    import android.app.ListFragment; 
    import android.app.LoaderManager; 
    import android.content.CursorLoader; 
    import android.content.Loader; 
    import android.content.res.Configuration; 
    import android.database.Cursor; 
    import android.net.Uri; 
    import android.os.Bundle; 
    import android.provider.BaseColumns; 
    import android.provider.ContactsContract; 
    import android.util.Log; 
    import android.view.LayoutInflater; 
    import android.view.Menu; 
    import android.view.MenuInflater; 
    import android.view.MenuItem; 
    import android.view.MenuItem.OnMenuItemClickListener; 
    import android.view.View; 
    import android.view.ViewGroup; 
    import android.widget.CursorAdapter; 
    import android.widget.LinearLayout; 
    import android.widget.ListView; 
    import android.widget.SimpleCursorAdapter; 
 
 
    /** 
     * @author default-name 
     * 
     */ 
    /** 
     * @author default-name 
     * 
     */ 
    public class PickFragment extends ListFragment implements 
        LoaderManager.LoaderCallbacks<Cursor>, OnMenuItemClickListener { 
 
        // Turn logging on or off 
        private static final boolean L = true; 
         
        // String for logging the class name 
        private final String CLASSNAME = getClass().getSimpleName(); 
         
        // Tag my loader with this ID 
        public static final int LOADER_ID = 42; 
         
        // Labels for members saved as state 
        private final String STATE_LABEL_NAME = "tablename"; 
         
        //The current table's class name 
        private String tableName; 
 
 
        public void onAttach(Activity activity) { 
            super.onAttach(activity); 
             
            // Notification that the fragment is associated with an Activity 
            if (L) 
                Log.i(CLASSNAME, "onAttach " + activity.getClass().getSimpleName()); 
        } 
 
        public void onCreate(Bundle savedInstanceState) { 
            super.onCreate(savedInstanceState); 
 
            // Tell the system we have an options menu 
            setHasOptionsMenu(true); 
             
            doLoaderCreation(savedInstanceState); 
             
            // Notification that 
            if (L) 
                Log.i(CLASSNAME, "onCreate"); 
        } 
 
        @Override 
        public View onCreateView(LayoutInflater inflater, ViewGroup container, 
                Bundle savedInstanceState) { 
 
            final LinearLayout listLayout = (LinearLayout) inflater.inflate( 
                    R.layout.list_frag_list, container, false); 
            if (L) 
                Log.i(CLASSNAME, "onCreateView"); 
  
            return listLayout; 
        } 
         
        @Override 
        public void onSaveInstanceState(Bundle outState) { 
            super.onSaveInstanceState(outState); 
             
            outState.putString(STATE_LABEL_NAME, tableName); 
        } 
 
        public void onStart() { 
            super.onStart(); 
            if (L) 
                Log.i(CLASSNAME, "onStart"); 
        } 
 
        public void onresume() { 
            super.onResume(); 
            if (L) 
                Log.i(CLASSNAME, "onResume"); 
        } 
 
        public void onPause() { 
            super.onPause(); 
            if (L) 
                Log.i(CLASSNAME, "onPause"); 
        } 
 
        public void onStop() { 
            super.onStop(); 
            if (L) 
                Log.i(CLASSNAME, "onStop"); 
        } 
 
        public void onDestroyView() { 
            super.onDestroyView(); 
            if (L) 
                Log.i(CLASSNAME, "onDestroyView"); 
        } 
 
        public void onDestroy() { 
            super.onDestroy(); 
            if (L) 
                Log.i(CLASSNAME, "onDestroy"); 
        } 
 
        public void onDetach() { 
            super.onDetach(); 
            if (L) 
                Log.i(CLASSNAME, "onDetach"); 
        } 
 
    // //////////////////////////////////////////////////////////////////////////// 
    // Minor lifecycle methods 
    // //////////////////////////////////////////////////////////////////////////// 
 
        public void onActivityCreated() { 
            // Notification that the containing activiy and its View hierarchy exist 
            if (L) 
                Log.i(CLASSNAME, "onActivityCreated"); 
        } 
 
    // /////////////////////////////////////////////////////////////////////////////
    // Overrides of the implementations of ComponentCallbacks methods in Fragment 
    // /////////////////////////////////////////////////////////////////////////////
 
        @Override 
        public void onConfigurationChanged(Configuration newConfiguration) { 
            super.onConfigurationChanged(newConfiguration); 
 
            // This won't happen unless we declare changes we handle in the manifest 
            if (L) 
                Log.i(CLASSNAME, "onConfigurationChanged"); 
        } 
 
        @Override 
        public void onLowMemory() { 
            // No guarantee this is called before or after other callbacks 
            if (L) 
                Log.i(CLASSNAME, "onLowMemory"); 
        } 
 
    // /////////////////////////////////////////////////////////////////////////////
    // ListFragment click handling 
    // /////////////////////////////////////////////////////////////////////////////
 

Handling Clicks in the List View

The onListItemClick method (in Listing 8-2) implements the callback for the way a list fragment handles clicks in the list view contained in the fragment.

In the case of this application, data about the database and data about the row selected are prepared and displayed in the tabs. The data is placed in a Bundle object in case the tabs are in a separate activity, in order to support the way that this application approaches the problem of scaling across small- and large-screen devices.


NOTE Chapter 1 explains this approach in detail.

LISTING 8-2: Handling clicks


        public void onListItemClick(ListView l, View v, int position, long id) { 
            Cursor c = ((CursorAdapter) getListView().getAdapter()).getCursor(); 
            String item = buildItemInfo(c, position); 
            String tableInfo = buildDatabaseInfo(c); 
            Bundle data = ((MainActivity) getActivity()).buildDataBundle(item, 
                   tableInfo); 
            ((TabbedActivity) getActivity()).loadTabFragments(data); 
        } 
 
    // //////////////////////////////////////////////////////////////////////////// 
    // Implementation of LoaderCallbacks 
    // //////////////////////////////////////////////////////////////////////////// 
 

Implementing the Loader Callbacks Interface

This application uses a Loader and LoaderManager to handle long-running code — in this case the code for accessing a content provider. If you have a large number of contacts, you can readily see that querying a content provider would result in hanging the UI for the duration it takes the query to run. The CursorLoader used here prevents the UI from locking up by performing query operations on a separate thread.

In Listing 8-3 you can see implementations for all three Loader callback methods.

  • OnCreateLoader creates and returns the CusorLoader object initialized by a query from the specified table, taken from the member tableName.
  • The onLoadFinished method gets a reference to the Adapter object, which puts data into the list’s views and sets the new cursor for the list.
  • The onLoaderReset call sets the cursor to null.

LISTING 8-3: Implementing Loader callbacks


        // Create the loader, passing in the query 
        public Loader<Cursor> onCreateLoader(int id, Bundle args) { 
            return new CursorLoader(this.getActivity(), uriForTable(tableName), 
                    null, null, null, null); 
        } 
         
        // Get results 
        public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { 
            ((SimpleCursorAdapter) getListAdapter()).swapCursor(cursor); 
        } 
         
        // Reset 
        public void onLoaderReset(Loader<Cursor> loader) { 
            ((SimpleCursorAdapter) getListAdapter()).swapCursor(null); 
            
        } 
         
    // //////////////////////////////////////////////////////////////////////////// 
    // App-specific code 
    // //////////////////////////////////////////////////////////////////////////// 
         
        private final static String NL = System.getProperty("line.separator"); 
         
         
        /** 
         * Called from onCreate. restore state if available 
         * 
         * @param savedInstanceState 
         */ 
        private void doLoaderCreation(Bundle savedInstanceState) { 
            super.onCreate(savedInstanceState); 
            
            // If no saved state, start fresh with the Data table 
            if (null == savedInstanceState) { 
                openNewTableByName("android.provider.ContactsContract$Data"); 
            } else { 
                // See if we can recreate the query from saved state 
                tableName = savedInstanceState.getString(STATE_LABEL_NAME); 
                if (null == tableName) { 
                    doLoaderCreation(null); // Nope 
                } else { 
                    openNewTableByName(tableName); 
                } 
            } 
       
       
        } 
 

Opening a New Table

In Listing 8-4, the method openNewTableByName calls some other methods that turn a class name into a URI, and result in a query based on that URI. The uriForTable method goes from class name to URI object. The newTableQuery method starts the query operation.

LISTING 8-4: Opening a new table


        /** 
         * Open a new table, based on the name of the class 
         * from the API. 
         * 
         * @param tableClassName 
         */ 
        private void openNewTableByName(String tableClassName) { 
 
            Uri table = uriForTable(tableClassName); 
            if (null == table) {return;} // Fail silently 
            tableName = tableClassName; 
            newTableQuery(table, ListColumnMap.get(table)); 
        } 
 

From a Name to a Query

In Listing 8-5, you can see the methods that go from the name of a class to a query for the contents of a table corresponding to the CONTENT_URI URI object in that class. The name of the class comes from a menu selection, built from the content provider’s own information, so error checking here is minimal.

The uriForTable method takes the information the app gets when a user makes a menu selection — which is the name of a class — and returns the CONTENT_URI object, which is a static object in that class.

First, the forName method of the class Class is used to turn the name of a class into a reference to the class object. Then a reference to the CONTENT_URI static object is returned.

Even though you should be confident that your class names and field names are valid, a lot can go wrong during the process of going from the string that names a class, to the class object itself, and then to a field, which is also retrieved by name. This method therefore catches all the exceptions that could result from an incorrect class or field name and fails silently. If a table selected by class name does not exist or cannot be accessed by the means used in this app, the app does nothing.

LISTING 8-5: Going from the name of a class to a query for the contents of a table


         
        /** 
         * Get the content uri, given the corresponding 
         * class name 
         * 
         * @param name The name of the class in ContactsContract 
         * @return The content uri for the corresponding class 
         */ 
        private Uri uriForTable(String name) { 
            Class<?> tableClass; 
            Uri table; 
             
            // Get the table's class 
            try { 
                tableClass = Class.forName(name); 
            } catch (ClassNotFoundException e) { 
                return null; 
            } 
             
            // Get the content Uri, which should be static, hence no instance for get
            try { 
                table = (Uri)(tableClass.getDeclaredField("CONTENT_URI").get(null)); 
            } catch (IllegalArgumentException e) { 
                return null; 
            } catch (IllegalAccessException e) { 
                return null; 
            } catch (NoSuchFieldException e) { 
                return null; 
            } 
            return table; 
        } 

From a URI to a Query

When you choose a table to explore in this app, two things happen. A Cursor object is created with all the data in the table, and the list on the left side of the screen is loaded with values that help you pick rows in that table. For example, contact names help you pick rows that correspond to information about the people with those names.

In Listing 8-6, the NewTableQuery method sets this process in motion. First, it determines whether the column name that is supposed to go in the list is null, in which case the _ID field of the BaseColumns interface is used. If that happens, the list will contain record ID numbers.

Next, arrays that specify the mapping of the named column to a view ID are created. Because there is only one column and one view being used, these arrays have one member each.

Then an Adapter instance is created. The subclass SimpleCursorAdapter is used, and it is provided with the arrays specifying the mapping of columns to views. The new adapter is set to be the adapter for the list view in this fragment.

Now that the ListView object is ready to display new data, a loader is created and started with the help of the LoaderManager object associated with this fragment. That new loader is assigned an ID, and any existing loader is destroyed.

LISTING 8-6: Going from a URI to a query for the contents of a table


        private void newTableQuery(Uri table, String column) { 
             
            if (null == column || column.isEmpty()) { 
                column = BaseColumns._ID; 
            } 
         
            String[] fromColumns = { column }; 
            int[] toViews = { android.R.id.text1 }; 
         
            // Create an adapter without a cursor 
            SimpleCursorAdapter adapter = new SimpleCursorAdapter(this.getActivity(),
                    android.R.layout.simple_list_item_1, null, 
                    fromColumns, toViews, 0); 
            setListAdapter(adapter); 
         
            // Make a new loader 
            LoaderManager m = getLoaderManager(); 
            if (null != m.getLoader(LOADER_ID)) { 
                m.destroyLoader(LOADER_ID); 
            } 
            m.initLoader(LOADER_ID, null, this); 
        } 
 

Formatting Data for Display

In Listing 8-7, two methods for formatting data are defined: buildItemInfo returns a string that contains all the data from a row in a database, and buildDatabaseInfo returns a string containing the number of rows and columns in a database. In both these methods, as with other code in this application, you want to avoid entering a lot of strings for the purpose of labeling, and you want to avoid table-specific UI constructs. So each column in the table gets one line of output in the Item fragment, and is labeled with the name of the column.

You can see you get the names of the columns from the Cursor object. Then you iterate over the columns, get the data for the specified row and column, and concatenate this to a string that’s returned.

LISTING 8-7: Formatting data for display


        /** 
         * Extracts, labels, and formats all the information in 
         * all the columns in a row. 
         * 
         * @param c The cursor 
         * @param position The position in the cursor 
         * @return The formatted data from the row 
         */ 
        private String buildItemInfo(Cursor c, int position) { 
             
            int i; 
            int columns = c.getColumnCount(); 
            String info = ""; 
             
            c.moveToPosition(position); 
            String names[] = c.getColumnNames(); 
 
            for (i = 0; i < columns; i++) { 
                info += names[i] + ": "; 
                try { 
                    info += c.getString(i); 
                } catch (Exception e) { 
                    // Fail silently 
                } 
                info += NL; 
            } 
             
            return info; 
        }     
         
        private String buildDatabaseInfo (Cursor c) { 
            String info = ""; 
             
            info += getString(R.string.column_count_label) + c.getColumnCount() + NL;
            info += getString(R.string.row_count_label) + c.getCount() + NL; 
                     
            return info; 
             
        } 
         
    /////////////////////////////////////////////////////////////////////////////// 
    // Methods for transferring data between Fragments 
    /////////////////////////////////////////////////////////////////////////////// 
         
 

Packaging Data for Tabs

In Listing 8-8, the method buildDataBundle packages the data in a way that one or two fragments can appear on the screen at one time. If the screen is small and only one fragment is displayed, and the user clicks on a list item, a new activity is started to display the Item and Table tabs. A single string contains all the data for each tab. These strings are placed in two places in the Bundle object, each with names specified in the XML file that specifies the layout for each tab.

LISTING 8-8: Packaging data for tabs

        /** 
         * Build a Bundle that holds the database and item information 
         * 
         * @param item Information about the selected row 
         * @param dbInfo Information about the database 
         * @return the Bundle containing the above information 
         */ 
        public Bundle buildDataBundle(String item, String dbInfo) { 
            Bundle data = new Bundle(); 
             
            data.putString(getDataLabel(R.id.item_frag), item); 
            data.putString(getDataLabel(R.id.detail_frag), dbInfo); 
            return data; 
             
        } 
         
        public String getDataLabel(int id) { 
            Fragment frag = getFragmentManager().findFragmentById(id); 
            String label = ((TabbedActivity.SetData)frag).getDataLabel(); 
            return label; 
        } 
 
         
    // /////////////////////////////////////////////////////////////////////////////
    // Menu handling code, including implementation of onMenuItemClickListener 
    // /////////////////////////////////////////////////////////////////////////////
 

Creating the Menu

The menu for selecting a table is created in the onCreateOptionsMenu method in Listing 8-9. You could type in all the table and column names from the documentation, but that defeats the purpose of exploring this large and unusual API by writing an app.

LISTING 8-9: Creating the menu

        public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 
            buildTableMenu(menu);
            super.onCreateOptionsMenu(menu, inflater);
        }

Selecting a Table from the Option Menu

In Listing 8-10 the onMenuItemClickListener method handles the selection of a table name from the menu and calls the openNewTableByName method. In the previous listing, you could see when the menu is created, and shortly you will see the code for building the menu.

LISTING 8-10: Selecting a table


        @Override 
        public boolean onMenuItemClick(MenuItem item) { 
            openNewTableByName((String) item.getTitle()); 
            return true; 
        } 
         
 
    // //////////////////////////////////////////////////////////////////////////// 
    // App-specific code to create a menu of tables 
    // //////////////////////////////////////////////////////////////////////////// 
         

Finding Possible Tables

Instead of hard-coding table names and copying them from the API documentation, all the tables are enumerated in a general-purpose way, with a method called buildTableMenu (see Listing 8-11). This will also enable you to see if there are undocumented tables in this database.

First, an array of all the classes in ContactsContract is created by using the getClasses method of the ContactsContract class object. This array might contain classes that don’t correspond to any table. An array isn’t the best way to filter out everything that isn’t a table, so on the next line an array list is created using the array of classes.

Building an Option Menu for Tables

Now that the list of tables has been built and scrubbed in Listing 8-11, the buildTableMenu method uses the add method of the Menu class to add each table name to the option menu and the setOnMenuItemClickListener method to set this fragment as the click listener for the menu item.

LISTING 8-11: Finding tables and building an Option menu

        /** 
         * Add a MenuItem to the specified menu for each table in the 
              ContactsContract 
         * class 
         * 
         * @param menu 
         */ 
        private void buildTableMenu(Menu menu) { 
            Class<?>[] tablesArray = ContactsContract.class.getClasses(); 
            ArrayList<Class<?>> tablesList = new ArrayList<Class<?>>
                   (Arrays.asList(tablesArray)); 
            deleteNonTables(tablesList); 
            for (Class<?> c : tablesList) { 
                menu.add(c.getName()).setOnMenuItemClickListener(this); 
                } 
        } 
 

Scrubbing the Menu of Tables

Previously, the deleteNonTables method was called from buildTableMenu. In Listing 8-12, this method scrubs the list of possible tables and eliminates those that can’t be tables. Interfaces can’t represent tables. The isInterface method of the Class object determines whether a class is an interface and, if so, drops it from the list.

LISTING 8-12: Scrubbing the menu list of tables

        /** 
         * Delete the embedded classes of ContactsContract that are not tables 
         * i.e. not the interfaces, and not the classes that do not implement 
         * BaseColumns. 
         * 
         * This might miss tables that do not, in fact, implement BaseColumns 
         * but are still tables (but not ones that follow ContactsContract API 
         * conventions) 
         * 
         * @param tablesList The raw list of embedded classes 
         */ 
        private void deleteNonTables(ArrayList<Class<?>> tablesList) { 
            ListIterator<Class<?>> l = tablesList.listIterator(); 
            while (l.hasNext()) { 
                Class<?> c = l.next(); 
                 
                // Might be belt-and-suspenders 
                if (true == c.isInterface()) { 
                    l.remove(); 
                } 
                else if (false == implementer(c, BaseColumns.class)) { 
                    l.remove(); 
                } 
            } 
        } 
 

The implementer method (Listing 8-13) is called to test if the specified class implements the specified interface. The implementer method gets an array of interfaces for the specified class. Iterating over the array determines whether the class includes the interface. But it does not determine whether a parent class includes the interface, so this method recurses to walk the inheritance hierarchy for the specified class.

LISTING 8-13: The implementer method


        /** 
         * Does the specified class implement the specified interface? 
         * 
         * @param c The class 
         * @param interf The interface 
         * @return True if the interface is implemented by the class 
         */ 
        private boolean implementer(Class<?> c, Class<?> interf) { 
            for (Class<?> ci : c.getInterfaces()) { 
                if (ci.equals(interf)) { 
                    return true; 
                } else { 
                    // Recurse, getInterfaces only gets one level (?!) 
                    if (true == implementer(ci, interf)) { 
                        return true; 
                    } 
                } 
            } 
            return false; 
        } 
       
         
    } 

SUMMARY

In this chapter you learned how to automatically find out about the tables in and explore one of the most complex SQLite databases you are likely to encounter in Android programming. Though complex, it is also a notably small database — usually no more than a few thousand rows in any table.

The setting for this kind of database is also unusual: It exists in a mobile handset, managed by the SQLite library. Rarely is a SQL database so intensively designed when it holds a relatively small amount of data. You can consider it the pinnacle of an unusual breed: the PIM database.

However, that is not to say it is just a curiosity. You may want to use it in your applications, and knowing it thoroughly will help in both scoping the possibilities when using the Contacts provider and in designing apps that use it.

And, not least of all, it serves as a contrast to the approach in the rest of this book, which emphasizes JSON over multiple tables for flexibility and simplicity of design.

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

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