Android Custom Content Provider Introduction

The android content provider is used to share data between different android apps. Generally, android app data is saved in SQLite database tables. Use content provider, you can choose which table or view’s data to be shared with other apps. This can make your android app data more safety.

1. Android Custom Content Provider Overview.

  1. The common architecture of android app data sharing is as below.
    android-app-share-data-architecture
  2. From the picture, we can see that if you want to create a custom content provider which can expose your app data to others, you should do the following things.
  3. Design your app SQLite database table, make sure which table or view data you want to share.
  4. Create custom content provider class that extends class android.content.ContentProvider. And configure it in the AndroidManifest.xml file ( This can be done automatically if you use android studio).
  5. Override android.content.ContentProvider‘s onCreate, insert, update, delete and query method to implement SQLite database CRUD operation.

2. How To Create Custom Content Provider Class In Android Studio.

  1. Right-click the package folder in the android studio left project panel. Commonly the last folder name in the package path is the provider.
  2. Click New —> Other —> Content Provider menu item.
  3. Input the Class Name ( for example AccountContentProvider ) and URI Authorities ( for example com.dev2qa.account.provider ) value in the Configure Component wizard dialog. The URI Authorities are used in content URI and URI mime-type.
  4. Check both the Exported and Enabled checkboxes. Enabled means this content provider is working while Exported means this content provider can be accessed by other apps.
  5. Click Finish button, then you can see below custom content provider class has been generated.
    public class AccountContentProvider extends ContentProvider {
        @Override
        public boolean onCreate() {
            return false;
        }
    
        @Nullable
        @Override
        public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
            return null;
        }
    
        @Nullable
        @Override
        public String getType(@NonNull Uri uri) {
            return null;
        }
    
        @Nullable
        @Override
        public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
            return null;
        }
    
        @Override
        public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
            return 0;
        }
    
        @Override
        public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
            return 0;
        }
    }
  6. You can also find that android studio has added the below custom content provider configuration settings in the AndroidManifest.xml file. Please remember the value of the android:authorities attribute, it will be used later.
    <provider
        android:name=".datasharing.custom.provider.AccountContentProvider"
        android:authorities="com.dev2qa.account.provider"
        android:enabled="true"
        android:exported="true"></provider>

3. Custom Android Content Provider Override Methods Introduction.

3.1 onCreate Method.

  1. This method is invoked when the content provider is initialized. We usually execute app SQLite database table create and upgrade actions in this method.
  2. If the action is successful without error, onCreate method will return true, otherwise, it will return false.
  3. Please note that the content provider is initialized only when the ContentResolver tries to access it use the content provider provided Uri.

3.2 Cursor query(Uri uri, String []columns, Stirng whereClause, Stirng []whereClausePlaceHolderValue, String orderBy).

  1. This method will be invoked when the client app content resolver wants to query this content provider shared data.
  2. Uri uri: Used to specify which table to query. The uri string format is commonly like ‘content://com.d…m.provider/table1′ ( means query for entire table1 ) or ‘content://com.d…m.provider/table1/1’ ( means query for the row which _id column value is 1 ).
  3. String []columns: Specify which column’s value that will be returned to the client content resolver.
  4. String whereClause: This is the query condition that is used to filter the query result. It is similar to the where clause in the SQL command.
  5. String []whereClausePlaceHolderValue: If you use placeholder ( ? ) character in the where clause, then this is the placeholder string value array. If you do not use a placeholder in the where clause, this parameter value should be null.
  6. String orderBy: This is the order by clause in the query like SQL command.
  7. The return value should be an android.database.Cursor object. The client app content resolver class will use the below code to loop the result in the cursor.
    if(cursor!=null)
    {
        // Get the result count.
        int queryResultCount = cursor.getCount();
        // below check can avoid cursor index out of bounds exception. android.database.CursorIndexOutOfBoundsException
        if(queryResultCount > 0)
        {
            // Move to the first row in the result cursor.
            cursor.moveToFirst();
            do{
               ......
               long rawContactId = cursor.getLong(cursor.getColumnIndex(ContactsContract.RawContacts._ID));
            }while(cursor.moveNext())      
        }
    }

3.3 Uri insert(Uri uri, ContentValues contentValues).

  1. Insert one row of data into the content provider, and return a Uri object that is used to represent this newly inserted record.
  2. Uri uri: Specify which table that the data will be inserted into.
  3. ContentValues contentValues: Store the column name and values that will be inserted.
    // Insert data into content provider example code.
    
    // Prepare content values.
    ContentValues contentValues = new ContentValues();
    contentValues.put("name", "jerry");
    contentValues.put("notes", "He is a experience android developer.");
    
    // insert content values into employee table
    Uri uri = contentResolver.insert(Uri.parse("content://com.dev2qa.provider/employee"), contentValues);
    
    // Get the newly created employee id use the returned uri object.
    long employeeId = ContentUris.parseId(uri);

3.4 int delete(Uri uri, String whereClause, String[] whereClausePlaceHolderValues).

  1. Delete the rows in URI specified SQLite table.
  2. Uri uri: Specify which table data row will be deleted.
  3. String whereClause: Delete rows condition, like SQL where clause, you can use place holder character ( ? ) in it.
  4. String[] whereClausePlaceHolderValues: This is the placeholder value array if you use place holder in the where clause, it can also be null which means there is no place holder in where clause.
  5. The return value is the count of deleted rows.
    // Invoke content provider delete method example code.
    
    ContentResolver contentResolver = getContentResolver();
    
    contentResolver.delete(Uri.parse("content://com.dev2qa.provider/employee"), " name = 'jerry' ", null);

3.5 int update(Uri uri, ContentValues contentValues, String whereClause, String[] whereClausePlaceHolderValues).

  1. Update exist row column values in URI specified table.
  2. The first two input parameters are similar to the insert method, and the last two input parameters are similar to the delete method. The return value is the updated rows count.
    // Create content values object.
    ContentValues contentValues = new ContentValues();
    
    // Put new employee notes.
    contentValues.put("notes", "This guy is a very good android developer.");
    
    // Create query condition, query with the employee name.
    StringBuffer whereClauseBuf = new StringBuffer();
    
    whereClauseBuf.append("name = 'jerry' ");
    
    // Update employee notes values by name condition and return update row count..
    int updateCount = contentResolver.update(Uri.parse("content://com.dev2qa.provider/employee"), contentValues, whereClauseBuf.toString(), null);

3.6 String getType(Uri uri)

  1. This method return URI object data mime-type. One URI map to one mime-type. The mime-type value is a string value that has below three parts.
  2. Start with vnd.
  3. If the URI ends with the path then add android.cursor.dir/ after vnd.
  4. If the URI ends with table row id then add android.cursor.item/.
  5. Then add vnd.<authority>.<path> at the end of the string. authority value is configured in the Android Manifest XML file. path value is table name in general.
  6. So for a uri such as content://com.dev2qa.provider/employee, it’s mime-type value is vnd.android.cursor.dir/vnd.com.dev2qa.provider.employee.
  7. For a uri such as content://com.dev2qa.provider/employee/1, it’s mime-type value is vnd.android.cursor.item/vnd.com.dev2qa.provider.employee.

4. Content URI.

  1. Content URI is passed by ContentResolver, it will tell the content provider which table ( by table name ) or which table row (by a table name and row _id ) need to be operated ( such as insert, update, delete, and query ).
  2. Below are standard content URI examples.
    content://<authority>/table_name or content://<authority>/table_name/row_id.
    
    content://com.dev2qa.provider/employee : This means the caller want to access all the data in employee table.
    content://com.dev2qa.provider/employee/1 : This means the caller want to access data row which _id columns's value is 1 in employee table.
  3. You can also use the wildcard to match the above two types of URI.
    * : Match any characters of any length.
    # : Match any integer numbers.
  4. So you can create URI like below.
    content://com.dev2qa.provider/*: This means the caller needs to access any table.
    content://com.dev2qa.provider/employee/#: This Means the caller wants to access all rows in the employee table.

5. How To Parse Content Resolver Passed Uri.

  1. One of your custom content provider’s function is to parse content resolver passed URI to know which table or rows that client app want to access.
  2. You can use android.content.UriMatcher class to achieve this goal as below.
  3. First, create a UriMatcher instance without matches input.
    private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
  4. Then invoke UriMatcher‘s addURI() method to add some custom content provider URI in it.
  5. The addURI() method has three input parameters as below.
    Content uri authority.
    Content uri path ( can use wildcard ( * or # )).
    User defined code.
  6. This means you can control which data to share to other apps by URI, this can make data security stronger and avoid important data leak risks.
    public static final int EMPLOYEE_DIR = 0;
    
    public static final int EMPLOYEE_ITEM = 1;
    
    public static final int MANAGER_DIR = 2;
    
    public static final int MANAGER_ITEM = 3;
    
    static{
        uriMatcher.addURI("com.dev2qa.provider", "employee", EMPLOYEE_DIR);
    
        uriMatcher.addURI("com.dev2qa.provider", "employee/#", EMPLOYEE_ITEM);
    
        uriMatcher.addURI("com.dev2qa.provider", "manager", MANAGER_DIR); 
    
        uriMatcher.addURI("com.dev2qa.provider", "manager/#", MANAGER_ITEM);
    }

5.3 Use UriMatcher’s match() method to parse and return client app passed URI’s user-defined code.

  1. With user-defined code, you can know which table or table row that client content resolver wants to access.
    public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
    
        // Parse uri and return user defined code.
        int userDefinedCode = uriMatcher.match(uri);
        
        if(userDefinedCode==EMPLOYEE_DIR)
        {
            // Operate employee table all row data.
        }else if(userDefinedCode==EMPLOYEE_ITEM)
        {
            // Access employee table single row.
        }else if(userDefinedCode==MANAGER_DIR)
        {
            // Process manager table all row data.
        }else if(userDefinedCode==MANAGER_ITEM)
        {
            // Process manager table single row.
        }

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.