Room Database with Content Provider in Java- Part1

Rashmi Rangaswamaiah
6 min readMar 29, 2020

To supply data from one application to another application on request, we need to make use of the Android component called Content Provider. A content provider can use different ways to store its data such as a database, in files, or over a network.

Overview of Content Provider

In this article, we will use Room to query a database and expose the data to another application through a content provider.

ROOM

The ROOM persistence library is part of the Android Architecture Components. The purpose of using Room is to make it easier to work with SQLiteDatabase objects in your app. It provides an abstraction layer over SQLite to allow for a more robust database, reduces the amount of boilerplate code, and verifies SQL queries at compile time.

Room Database

To use ROOM, add the following dependencies to your app’s build.gradle file.

implementation ‘androidx.room:room-runtime:2.2.3’
implementation ‘androidx.work:work-runtime:2.3.1’
annotationProcessor ‘androidx.room:room-compiler:2.2.3’

There are 3 major components in the Room such as Entity, DAO, and Database.

Entity

An entity represents a table within the database. It is represented using the @Entity annotation.

  • set the tableName property of the @Entity annotation to have a different table name.
  • Each entity should have at least 1 field as a primary key. This needs to be annotated with the @PrimaryKey annotation. If we want Room to assign automatic ID’s to entities, we set the @PrimaryKey’s autoGenerate property.
  • To have a different column name add the @ColumnInfo annotation to a field.

Lets’ have an entity to represent a person:

@Entity(tableName = "person_info")
public class Person {

@PrimaryKey(autoGenerate = true)
private Integer id;

@ColumnInfo(name = "name")
private String name;

@ColumnInfo(name = "gender")
private String gender;

@ColumnInfo(name = "emailAddress")
private String emailAddress;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getGender() {
return gender;
}

public void setGender(String gender) {
this.gender = gender;
}

public String getEmailAddress() {
return emailAddress;
}

public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}

public static Person fromContentValues
(ContentValues contentValues) {
Person person = new Person();
if (contentValues.containsKey(PERSON_ID)) {
person.setId(contentValues.getAsInteger(PERSON_ID));
} if (contentValues.containsKey(PERSON_NAME)) {
person.setName(contentValues.getAsString(PERSON_NAME));
} if (contentValues.containsKey(PERSON_GENDER)) {
person.setGender
(contentValues.getAsString(PERSON_GENDER));
} if (contentValues.containsKey(PERSON_EMAIL_ADDRESS)) {
person.setEmailAddress
(contentValues.getAsString(PERSON_EMAIL_ADDRESS));
}
return person;
}
}

DAO

In order to expose the data we first need to access the data. For this purpose, we use DAO, Data Access Object which is basically an interface or an object that provides access to an underlying database. It contains the methods used for accessing the database. Annotate interface with @Dao.

@Dao
public interface PersonDao {
/**
* Insert a person data into the table
* @return row ID for newly inserted data
*/
@Insert
long insert(Person person);
/**
* select all person
* @return A {@link Cursor} of all person in the table
*/
@Query("SELECT * FROM person_info")
Cursor findAll();
/**
* Delete a person by ID
* @return A number of persons deleted
*/
@Query("DELETE FROM person_info WHERE id = :id ")
int delete(long id);
/**
* Update the person
* @return A number of persons updated
*/
@Update
int update(Person person);
}

Return an object of the type Cursor, which can be easily used by the ContentProvider.

Database

It serves as the main access point for the connection to your application's persisted data.

To create a database we need to define an abstract class that needs to be annotated with @Database.

A class annotated with @Database should satisfy the following conditions:

  • This abstract class extends RoomDatabase.
  • Specify a list of entities associated with the database within the annotation.
  • Contains an abstract method with no arguments and returns the Dao class (annotated @Dao).
  • Room.databaseBuilder() is used to get an instance of Database.
  • version- specify the database version.
@Database(entities = {Person.class}, version = 1)
public abstract class ApplicationDatabase extends RoomDatabase {

private static final String DATABASE_NAME = "testdb";
private static ApplicationDatabase INSTANCE;

public static ApplicationDatabase getInstance(Context context){
if (INSTANCE == null) {
INSTANCE = Room
.databaseBuilder(context, ApplicationDatabase.class, DATABASE_NAME).build();
}
return INSTANCE;
}

public abstract PersonDao getPersonDao();

}

Now let’s create a content provider to expose the application data outside of our application.

Content Provider

Content provider class PersonContentProvider.java will be used to expose the data of database. The class PersonContentProvider inheriting from ContentProvider should implement the primary methods:

  • onCreate()- It is used to initialise the provider
  • query()- returns data to the caller
  • insert()- inserts new data in the content provider
  • update()- Updates the existing data in the content provider
  • delete()- deletes data from the content provider
  • getType(Uri)- returns the MIME type of data in the content provider
public class PersonContentProvider extends ContentProvider {

public static final String TAG = PersonContentProvider.class.getName();

private PersonDao personDao;

/**
* Authority of this content provider
*/
public static final String AUTHORITY = "com.example.democontentprovider.provider";

public static final String PERSON_TABLE_NAME = "person_info";

/**
* The match code for some items in the Person table
*/
public static final int ID_PERSON_DATA = 1;

/**
* The match code for an item in the PErson table
*/
public static final int ID_PERSON_DATA_ITEM = 2;

public static final UriMatcher uriMatcher = new UriMatcher
(UriMatcher.NO_MATCH);

static {
uriMatcher.addURI(AUTHORITY,
PERSON_TABLE_NAME,
ID_PERSON_DATA);
uriMatcher.addURI(AUTHORITY,
PERSON_TABLE_NAME +
"/*", ID_PERSON_DATA_ITEM);
}

@Override
public boolean onCreate() {
personDao = ApplicationDatabase.getInstance(getContext())
.getPersonDao();
return false;
}

@Nullable
@Override
public Cursor query(@NonNull Uri uri,
@Nullable String[] projection,
@Nullable String selection,
@Nullable String[] selectionArgs,
@Nullable String sortOrder) {
Log.d(TAG, "query");
Cursor cursor;
switch (uriMatcher.match(uri)) {
case ID_PERSON_DATA:
cursor = personDao.findAll();

if (getContext() != null) {
cursor.setNotificationUri(getContext()
.getContentResolver(), uri);
return cursor;
}

default:
throw new IllegalArgumentException
("Unknown URI: " + uri);

}
}

@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}

@Nullable
@Override
public Uri insert(@NonNull Uri uri,
@Nullable ContentValues values) {
Log.d(TAG, "insert");
switch (uriMatcher.match(uri)) {
case ID_PERSON_DATA:
if (getContext() != null) {
long id = personDao.insert(Person.
fromContentValues(values));
if (id != 0) {
getContext().getContentResolver()
.notifyChange(uri, null);
return ContentUris.withAppendedId(uri, id);
}
}
case ID_PERSON_DATA_ITEM:
throw new IllegalArgumentException
("Invalid URI: Insert failed" + uri);
default:
throw new IllegalArgumentException
("Unknown URI: " + uri);
}

}

@Override
public int delete(@NonNull Uri uri,
@Nullable String selection,
@Nullable String[] selectionArgs) {
Log.d(TAG, "delete");
switch (uriMatcher.match(uri)) {
case ID_PERSON_DATA:
throw new IllegalArgumentException
("Invalid uri: cannot delete");
case ID_PERSON_DATA_ITEM:
if (getContext() != null) {
int count = personDao
.delete(ContentUris.parseId(uri));
getContext().getContentResolver()
.notifyChange(uri, null);
return count;
}
default:
throw new IllegalArgumentException
("Unknown URI:" + uri);
}

}

@Override
public int update(@NonNull Uri uri,
@Nullable ContentValues values,
@Nullable String selection,
@Nullable String[] selectionArgs) {
Log.d(TAG, "update");
switch (uriMatcher.match(uri)) {
case ID_PERSON_DATA:
if (getContext() != null) {
int count = personDao
.update(Person.fromContentValues(values));
if (count != 0) {
getContext().getContentResolver()
.notifyChange(uri, null);
return count;
}
}
case ID_PERSON_DATA_ITEM:
throw new IllegalArgumentException
("Invalid URI: cannot update");
default:
throw new IllegalArgumentException
("Unknown URI: " + uri);
}

}
}

The content provider needs to be declared in an <provider> element in the manifest file. We need to declare those that are part of our application in order to make it accessible for other applications. If it is not defined then the system is not aware of content providers and doesn’t run them.

AndroidManifest.xml

<provider
android:authorities="com.example.democontentprovider.provider"
android:name=".provider.PersonContentProvider"
android:exported="true"/>

In order to test the Content Provider it is not necessary to create another application. The simple way to test is with the Instrumented Tests. The reader can refer to my part2 article regarding the testing of content provider.

--

--

Rashmi Rangaswamaiah

Developer | Traveller | Excited Learner | Indian | Kannadiga | Bengaluru-Germany-Belgium |