Tuesday, 5 July 2016

How to share data between apps using Content Provider in Android

On the Android platform, one application cannot directly access (read/write) other application's data. All persistent data of an app is private to that app. Every application has its own id data directory and own protected memory area. This means that an app cannot query or manipulate the data of another app. However, if you want to enable an app to query or manipulate the data of another app, you need to use the concept of content providers.

Content Provider:

Content provider is a set of data wrapped up in a custom API to read and write. It acts as an interface that allows you to store and retrieve data from a data source. And also allows you to share an app’s data with other apps. Content providers decouple the app layer from the data layer by abstracting the underlying data source, thereby making apps data-source independent. They allow full permission control by monitoring which app components or users should have access to the data making data sharing easy. As a result, any app with appropriate permissions can add, remove, update, and retrieve data of another app including the data in some native Android databases.

There are 2 types of Content Providers :

  • Custom content providers: These are created by the developer to suit the requirements of an app.
  • Native content providers: These provide access to built-in databases, such as contact manager, media player, and other native databases. You need to grant the required permissions to your app before using native content providers.
Eg : The WhatsApp app sharing mobile contacts data.
Media store-Allows other applications to access, store media files.



Above diagram shows how content provider works. Application 2 stores its data in its own database and provides a provider. Application 1 communicates with the provider to access Application 2's data.

Content Provider uses a special URI starting with content:// will be assigned to each content providers and that will be recognized across applications.

Creating a Content Provider :

To create a content provider we need to

  1. Create sub class for ContentProvider.
  2. Define content URI
  3. Implement all the unimplemented methods. insert(), update(), query(), delete(), getType().
  4. Declare the content provider in AndroidManifest.xml
Specifying the URI of a Content Provide

Content provider URI consists of four parts.
content://authority/path/id

Each content provider exposes a URI that uniquely identifies the content provider’s data set. If a content provider controls multiple datasets, a separate URI needs to be specified for each dataset. The URI of a content provider begins with the string, content://.

content:// - All the content provider URIs should start with this value

authorityA Java namespace of the content provider implementation. (fully qualified Java package name)

path - A virtual directory within the provider that identifies the kind of data being requested.

Id - Its optional part that specifies the primary key of a record being requested. We can omit this part to request all records.

Registering the provider in AndroidManifest.xml

As with any other major parts of Android application, we have to register the content providers too in the AndroidManifest.xml. <provider> element is used to register the content providers. It should be declared under <application>.

<provider
     android:name=".MyProvider"
     android:authorities="com.example.contentproviderexample.MyProvider"
     android:exported="true"
     android:multiprocess="true" >
</provider>

Here authorities is the URI authority to access the content provider. Typically this will be the fully qualified name of the content provider class.

Creating provider application:

This application will create a content provider and share its data. This uses SQLite database 'sharedDb'. It has a table called 'names'. The table names have two columns id, name.
The URI of this content provider is content://com.example.contentproviderexample.MyProvider/cte
We have layout to insert new records to the database table.


Step 1 : Create a new project by going to File ⇒ New Android Application Project. Fill all the details and name your activity as MainActivity. (eclipse)

Step 2 : Design the activity_main.xml layout for MainActivity under layout folder as shown below for capturing data.


activity_main.xml

<LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android"
    xmlns:tools = "http://schemas.android.com/tools"
    android:layout_width = "match_parent"
    android:layout_height = "match_parent"
    tools:context = "${relativePackage}.${activityClass}" >
  

    <TextView
        android:layout_width = "wrap_content"
        android:layout_height = "wrap_content"
        android:text = "Enter Name :"
        android:textAppearance = "?android:attr/textAppearanceSmall"/>



  <EditText
        android:id = "@+id/txtName"
        android:layout_width = "fill_parent"
        android:layout_height = "wrap_content"/>



  <Button
        android:id = "@+id/btnAdd"
        android:layout_width = "fill_parent"
        android:layout_height = "wrap_content"

        android:onClick = "onClickAddName"

        android:text = "Add Name"/>



</LinearLayout >


Step 3 : Creating a Custom Content Provider


To create your own content provider, you need to create a class that extends the abstract ContentProvider class and overrides its methods.

MyProvider.java

public class MyProvider extends  ContentProvider
{

static final String PROVIDER_NAME = "com.example.contentproviderexample.MyProvider";
static final String URL = "content://" PROVIDER_NAME + "/cte";
static final Uri CONTENT_URI = Uri.parse(URL);

static final String id = "id";
static final String name = "name";
static final String uriCode = 1;
static final String UriMatcher  uriMatcher;
private static HashMap<String,String> values;

static
{
  uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
  uriMatcher.addURI(PROVIDER_NAME ,"cte", uriCode);
  uriMatcher.addURI(PROVIDER_NAME ,"cte/*", uriCode);
}

private SQLiteDatabase db;
  static final String DATABASE_NAME = "sharedDb";
  static final String TABLE_NAME = "names";
  static final int DATABASE_VERSION = 1;
  static final String CREATE_DB_TABLE = " CREATE TABLE " + TABLE_NAME
      + " (id INTEGER PRIMARY KEY AUTOINCREMENT, "
      + " name TEXT NOT NULL);";

    @Override
    public boolean onCreate()
    {
        Context context = getContext();
        DatabaseHelper dbHelper = new DatabaseHelper(context);
        db = dbHelper.getWritableDatabase();
        if (db != null) {
         return true;
        }
        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder)
    {
            SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
            qb.setTables(TABLE_NAME);

            switch (uriMatcher.match(uri)) {
               case uriCode:
                qb.setProjectionMap(values);
              break;
            default:
             throw new IllegalArgumentException("Unknown URI " + uri);
            }

            if (sortOrder == null || sortOrder == "") {
             sortOrder = name;
            }
            Cursor c = qb.query(db, projection, selection, selectionArgs, null,
              null, sortOrder);
            c.setNotificationUri(getContext().getContentResolver(), uri);
            return c;
    }

    @Override
    public String getType(Uri uri)
    {
        switch (uriMatcher.match(uri)) {
            case uriCode :
             return "vnd.android.cursor.dir/cte";
            default:
            throw new IllegalArgumentException("Unsupported URI: " + uri);
        }
    }

    @Override
    public Uri insert(Uri uri, ContentValues values)
    {
        long rowID = db.insert(TABLE_NAME, "", values);

        if (rowID > 0) {
         Uri _uri = ContentUris.withAppendedId(CONTENT_URI, rowID);
         getContext().getContentResolver().notifyChange(_uri, null);
         return _uri;
        }
        throw new SQLException("Failed to add a record into " + uri);
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs)
    {
        int count = 0;
        switch (uriMatcher.match(uri)) {
        case uriCode :
         count = db.delete(TABLE_NAME, selection, selectionArgs);
         break;
        default:
         throw new IllegalArgumentException("Unknown URI " + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
    {
        int count = 0;
        switch (uriMatcher.match(uri)) {
          case uriCode :
           count = db.update(TABLE_NAME, values, selection, selectionArgs);
          break;
         default:
           throw new IllegalArgumentException("Unknown URI " + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }

  private static class DatabaseHelper extends SQLiteOpenHelper
    {
          DatabaseHelper(Context context)
        {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }
    
        @Override
        public void onCreate(SQLiteDatabase db)
        {
            db.execSQL(CREATE_DB_TABLE);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
        {
            db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
            onCreate(db);
        }
   }
}

Step 4 : Now open up the MainActivity  from src/package. Add the following code.

Note : onClickAddName() is the onClick attribute declared inside layout.

public void onClickAddName(View view)
{
        ContentValues values = new ContentValues();
        values.put(MyProvider.name, ((EditText) findViewById(R.id.txtName))
          .getText().toString());
        Uri uri = getContentResolver().insert(MyProvider.CONTENT_URI, values);
        ((EditText) findViewById(R.id.txtName)).setText("");
        Toast.makeText(getBaseContext(), "New record inserted", Toast.LENGTH_LONG)
          .show();
}

Creating request application:


This application is very simple, it will use the query() method to access the data stored in the Provider application. This application is having a layout to display the data.


Step 5 : Create a new project by going to File ⇒ New Android Application Project. Name it as 'RequestApp' Fill all the details and name your activity as MainActivity. (eclipse)

Step 6 : Design the activity_main.xml layout for MainActivity under layout folder as shown below for displaying data.

activity_main.xml

<LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android"
    xmlns:tools = "http://schemas.android.com/tools"
    android:layout_width = "match_parent"
    android:layout_height = "match_parent"
    tools:context = "${relativePackage}.${activityClass}" >
  

  <Button
        android:id = "@+id/btnRetrieve"
        android:layout_width = "fill_parent"
        android:layout_height = "wrap_content"

        android:onClick = "onClickDisplayNames"

        android:text = "Display Names"/>


<TextView
        android:layout_width = "wrap_content"
        android:id = "@+id/res"
        android:layout_height = "wrap_content"
        android:textAppearance = "?android:attr/textAppearanceMedium"/>



</LinearLayout >

Step 7 : Now open up the MainActivity  from src/package. Add the following code.
We have used CusrsorLoader to load the data.

MainActivity.java

import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.content.CursorLoader;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends ActionBarActivity implements LoaderManager.LoaderCallbacks<Cursor> 
{
    TextView resultView = null; 
    CursorLoader cursorLoader;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        resultView= (TextView) findViewById(R.id.res);
    }

    @Override
    public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1)
    {
        cursorLoader = new CursorLoader(this, Uri.parse("content://com.example.contentproviderexample.MyProvider/cte"), null, null, null, null);
        return cursorLoader;
    }

 public void onClickDisplayNames(View view)
    {
        getSupportLoaderManager().initLoader(1, null, this);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor)
    {
        if (cursor != null)
        {
            cursor.moveToFirst();
            StringBuilder res = new StringBuilder();
                  while (!cursor.isAfterLast()) {
                   res.append("\n"+cursor.getString(cursor.getColumnIndex("id"))+ "-"+ cursor.getString(cursor.getColumnIndex("name")));
                      cursor.moveToNext();
                  }
                  resultView.setText(res);
        }
        else
        {
            Toast.makeText(getBaseContext(), "No records", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
     public void onLoaderReset(Loader<Cursor> arg0)
    {     
    }
}

Source code : Click here to download.

Thanks a lot for reading...
Don't forget to share this post if you found it interesting!
If you find something to add to this post? or any other quick thoughts/hints that you think people will find useful? Share it in the comments & feedback's are most welcome.