How to use content providers between applications to share data

Hi! Hope this blog finds you well and at peace!
I will talk about content providers just as my topic suggests. We all know that a content provider is used to share data with other applications. For example: The contacts application provides a content provider for us to be able to use the database in our application. For more information, you could visit the Android Developer Site.

App description:

  • This is just a gist about the application, so then we will have two applications. App A that will fetch a list of all music/video files and store it into the database (content provider) and the other app (App B) would fetch this list and play the music/video file.

 

The development of both these apps would be done in the same way as we develop every other android app. I will just talk about the content provider portion and all the permissions needed to make both these apps to work well. (It’s the working of the content provider , or rather the step by step explanation of how a content provider works, that becomes one of the major interview question, this might help 🙂 )

App A:

In the db package create a file “FileContentProvider” and extend “ContentProvider” and implements the functions needed to be implemented (I generally put all database files in the db package). Let the class look as under:

public static final String PROVIDER_NAME = "com.dhara.example.provider.files";

The provider name is also referred to as the authority in the manifest, which is used to identify this content provider. Both these and also the value assigned in the manifest have to be the same. This is just the name of the content provider that you are creating, in case you use some other content provider you then don’t have to declare it in the manifest file.

public static final String URL = "content://" + PROVIDER_NAME + "/audiovideo/";
public static final Uri CONTENT_URI = Uri.parse(URL);

The content URI is made up of the authority and also the path. The authority in our case is the PROVIDER_NAME and the path is represented by “audiovideo” which refers to the table or the file name

static final String _ID = "_id";
public static final String FILE_NAME = "file_name";
public static final String FILE_PATH = "file_path";

These fields are the normal database fields that would be part of the content provider we create. So we have a unique key (primary key) which is _id, and also we store the file name and the file path.

private static HashMap<String, String> FILES_PROJECTION_MAP;

This one stores the columns that would be fetched, in this example all the columns are fetched.

static final int FILES = 1;
static final int FILE_ID = 2;

These are static values inside the switch case to return the correct type of Uri matched.

static final UriMatcher uriMatcher;
static{
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PROVIDER_NAME, "audiovideo", FILES);
uriMatcher.addURI(PROVIDER_NAME, "audiovideo/#", FILE_ID);
}

A content provider generally uses the URI Matcher and also the Content URI, so we define it as above.

Summary Part 1:

  • A Content URI describes the content provider with the help of the authority that identifies the content provider, and the path that points to the table or to the file name
  • A URI Matcher pattern is generally used to be able to handle the incoming content URIs or rather used to handle how people query the content provider for data.
  • A URI Matcher maps the content URI “patterns” to integer values making it easier for us to simple use a switch case to handle these

I will explain:

uriMatcher.addURI(PROVIDER_NAME, "audiovideo", FILES);

This line here means that it would fetch the whole of audiovideo table records

While

uriMatcher.addURI(PROVIDER_NAME, "audiovideo/#", FILE_ID);

This one would allow apps to query the content provider with an id, which would be mapped to the file id column. So it’s another way of saying:

select the_columns from audiovideo where file_id = the_id_provided

The addURI() method is used to map the content provider’s content URI to an integer value with the help of the match() function that returns the integer value.

private SQLiteDatabase db;
static final String DATABASE_NAME = "Media";
static final String MEDIA_TABLE_NAME = "audiovideo";
static final int DATABASE_VERSION = 1;
static final String CREATE_DB_TABLE =
" CREATE TABLE " + MEDIA_TABLE_NAME +
" (_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
" file_name TEXT NOT NULL, " +
" file_path TEXT NOT NULL);";

These are database related fields that we also would have when creating a normal SQLite database.

Create a database helper class that would actually create and manage the provider’s underlying data repository.

So then, the above method defines what would be executed when it receives a query from any application accessing this content provider.

We also have the getType() method that returns the Mime format associated to a Content URI, this mime format describes the kind of data returned.

We have three parts associated to the type:

  • Type: which is always vnd
  • Subtype:
    • cursor.dir if the URI pattern is for more than one row
    • cursor.item if the URI pattern is for a single row
  • Provider-specific part:
    • <name>.<type>

The name has to be unique which can be your package name, and the type also has to be unique which generally identifies the table associated with the URI.

There are many other mime types that you can provide, and more information can be obtained from Android developer’s site.

The insert query will look like above, where in we insert values using a content value. Once the new record has been added we notify the content provider for changes and return the Uri with the updated record.

The delete query will look like the one above.

We now have the content provider ready to use in our fragments or activities, but before using it, let’s add the following lines to the manifest else we might forget it.

<permission
android:name="com.dhara.example.provider.files.READ_DATABASE"
android:protectionLevel="normal"/>
<uses-permission
    android:name="android.permission.READ_EXTERNAL_STORAGE"/>

The permission specified using the permission tag is important. It spectifies that this application using a custom permission that would be of the type

"com.dhara.example.provider.files.READ_DATABASE"

Every other application that makes use of this permission with the reference to this content provider would be able to access the content provider.

The provider will be declared inside the application tag but outside the activity tag as under:

<provider
    android:authorities="com.dhara.example.provider.files"
    android:name=".db.FileContentProvider"
    android:readPermission="com.dhara.exam.provider.files.READ_DATABASE"
    android:protectionLevel="signature"
    android:grantUriPermissions="true"
    android:exported="true">
</provider>
  • The name specifies the class file that acts as the content provider.
  • The authority specifies the unique name that can be used to identify the content provider from the list of content providers available
  • Exported specifies if the content provider is available for other applications to use or not. In our case it would be a yes.
  • grantUriPermissions specifies whether or not applications that would ordinarily not have permission to access data be granted permission to access to do so.
  • protectionLevel is generally kept at signature level for the application

How then do we save data to the content provider? We can have a method to do so, and you can call it from wherever you wish to call it from depending on your requirements.

I have created two methods, one that fetched the list of media and audio files by filtering out the extensions to get only the list required. And the other method saves it to the content provider.

MyApp.getAppContext() – this method here simply gets the instance of the application context.

How do we fetch the items then?

I used a cursor loader in my application and one of the methods of the cursor loader looked like this:

cursorLoader = new CursorLoader(MyApp.getAppContext(),
FileContentProvider.CONTENT_URI,null,null,null,null);
return cursorLoader;

OR

Cursor cursor = MyApp.getAppContext().getContentResolver()
            .query(FileContentProvider.CONTENT_URI,null,null,null,null);

App B:

We now access the data part of the first app’s content provider and play the media file.

The manifest file like I said must contain the same permission as App A, and App A should generally be installed first, and then App B, however, since we are adding permissions to both the files, the order of which one gets installed first will not matter.

<uses-permission
       android:name="com.dhara.example.provider.files.READ_DATABASE">
</uses-permission>

For you to be able to access the content provider and data present in it you must know the provider name and also the table name, and the fields that you wish to access from the table.

So, in a constants file place these:

private static String FILE_NAME="file_name";
private static String PROVIDER_NAME = "com.dhara.example.provider.files";
private static String URL="content://" + PROVIDER_NAME + "/audiovideo";
public static Uri CONTENT_URI = Uri.parse(URL);

There you go, use the same lines using a cursor loader to access the records from the database

cursorLoader = new CursorLoader(MyApp.getAppContext(),
FileContentProvider.CONTENT_URI,null,null,null,null);
return cursorLoader;

OR

Cursor cursor = MyApp.getAppContext().getContentResolver()
           .query(FileContentProvider.CONTENT_URI,null,null,null,null);

And using FILE_NAME or the other query mapping you can then fetch a single record and start playing it using the MediaPlayer

In order to fetch the record based on the id, you could do the following:

Uri uri = Uri.parse(FileContentProvider.URL + Uri.encode(id));
Cursor cursor = getContentResolver().query(uri,null, null, null, null);

In case of any issues, queries, or if there is anything missing, you can comment 🙂

Advertisements