Working with content providers

We spoke briefly about content providers as Android components in Chapter 2, Building an Android Application - Tetris. While doing so, we established the fact that content providers help an application control access to data resources stored either within the application or within another app. In addition, we established that a content provider facilitates the sharing of data with another application via an exposed application programming interface.

A content provider behaves in a way that is similar to the behavior of a database. A content provider permits the insertion, deletion, editing, updating, and querying of content. These abilities are permitted by the use of methods such as insert(), update(), delete(), and query(). In many cases, data controlled by a content provider exists in a SQLite database.

A content provider for your application can be created in five easy steps:

  1. Create a content provider class that extends ContentProvider.
  2. Define a content URI address.
  3. Create a datasource that the content provider will interact with.  This datasource is usually in the form of a SQLite database. In cases where SQLite is the datasource, you will need to create a SQLiteOpenHelper and override its onCreate() in order to create the database that will be controlled by the content provider.
  4. Implement the required content provider methods.
  5. Register the content provider in your project's manifest file.

In all, there are six  methods that must be implemented by a content provider. These are:

  • onCreate(): This method is called to initialize the database
  • query(): This method returns data to the caller via a Cursor
  • insert(): This method is called to insert new data into the content provider
  • delete(): This method is called to delete data from the content provider
  • update(): This method is called to update data in the content provider
  • getType(): This method returns the MIME type of data in the content provider when called

In order to ensure you fully understand the workings of a content provider, let's create a quick example project that utilizes a content provider and a SQLite database. Create a new Android studio project named ContentProvider and add an empty MainActivity to it upon creation. Similar to all other applications created in this chapter, this example is simple in nature. The application allows a user to enter the details of a product (a product name and its manufacturer) in text fields and save them to a SQLite database. The user can then view the information of products that they previously saved with the click of a button.

Modify activity_main.xml to contain the following XML:

<?xml version="1.0" encoding="utf-8"?>
<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"
android:orientation="vertical"
android:gravity="center_horizontal"
android:padding="16dp"
tools:context="com.example.contentproviderexample.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/content_provider_example"
android:textColor="@color/colorAccent"
android:textSize="32sp"/>
<EditText
android:id="@+id/et_product_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="Product Name"/>
<EditText
android:id="@+id/et_product_manufacturer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="Product Manufacturer"/>
<Button
android:id="@+id/btn_add_product"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Add product"/>
<Button
android:id="@+id/btn_show_products"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Show products"/>
</LinearLayout>

After making the preceding modifications, add the following string resource to your project's strings.xml file:

<string name="content_provider_example">Content Provider Example</string>

Now create a ProductProvider.kt file in the com.example.contentproviderexample package and add the following content:

package com.example.contentproviderexample

import android.content.*
import android.database.Cursor
import android.database.SQLException
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.database.sqlite.SQLiteQueryBuilder
import android.net.Uri
import android.text.TextUtils

internal class ProductProvider : ContentProvider() {

companion object {

val PROVIDER_NAME: String = "com.example.contentproviderexample
.ProductProvider"
val URL: String = "content://$PROVIDER_NAME/products"
val CONTENT_URI: Uri = Uri.parse(URL)

val PRODUCTS = 1
val PRODUCT_ID = 2

// Database and table property declarations
val DATABASE_VERSION = 1
val DATABASE_NAME = "Depot"
val PRODUCTS_TABLE_NAME = "products"

// 'products' table column name declarations
val ID: String = "id"
val NAME: String = "name"
val MANUFACTURER: String = "manufacturer"
val uriMatcher: UriMatcher = UriMatcher(UriMatcher.NO_MATCH)
val PRODUCTS_PROJECTION_MAP: HashMap<String, String> = HashMap()

SQLiteOpenHelper class that creates the content provider's database:


private class DatabaseHelper(context: Context) :
SQLiteOpenHelper(context, DATABASE_NAME, null,
DATABASE_VERSION) {

override fun onCreate(db: SQLiteDatabase) {
val query = " CREATE TABLE " + PRODUCTS_TABLE_NAME +
" (id INTEGER PRIMARY KEY AUTOINCREMENT, " +
" name VARCHAR(255) NOT NULL, " +
" manufacturer VARCHAR(255) NOT NULL);"

db.execSQL(query)
}

override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int,
newVersion: Int) {
val query = "DROP TABLE IF EXISTS $PRODUCTS_TABLE_NAME"

db.execSQL(query)
onCreate(db)
}
}
}

private lateinit var db: SQLiteDatabase

override fun onCreate(): Boolean {
uriMatcher.addURI(PROVIDER_NAME, "products", PRODUCTS)
uriMatcher.addURI(PROVIDER_NAME, "products/#", PRODUCT_ID)

val helper = DatabaseHelper(context)

Let's use the SQLiteOpenHelper to get a writable database; a new database is created if one does not already exist:

    db = helper.writableDatabase

return true
}

override fun insert(uri: Uri, values: ContentValues): Uri {
//Insert a new product record into the products table

val rowId = db.insert(PRODUCTS_TABLE_NAME, "", values)

//If rowId is greater than 0 then the product record was added successfully.

if (rowId > 0) {
val _uri = ContentUris.withAppendedId(CONTENT_URI, rowId)
context.contentResolver.notifyChange(_uri, null)

return _uri
}

// throws an exception if the product was not successfully added.
throw SQLException("Failed to add product into " + uri)
}

override fun query(uri: Uri, projection: Array<String>?,
selection: String?, selectionArgs: Array<String>?,
sortOrder: String): Cursor {

val queryBuilder = SQLiteQueryBuilder()
queryBuilder.tables = PRODUCTS_TABLE_NAME

when (uriMatcher.match(uri)) {
PRODUCTS -> queryBuilder.setProjectionMap(PRODUCTS_PROJECTION_MAP)
PRODUCT_ID -> queryBuilder.appendWhere(
"$ID = ${uri.pathSegments[1]}"
)
}

val cursor: Cursor = queryBuilder.query(db, projection, selection,
selectionArgs, null, null, sortOrder)

cursor.setNotificationUri(context.contentResolver, uri)
return cursor
}

override fun delete(uri: Uri, selection: String,
selectionArgs: Array<String>): Int {

val count = when(uriMatcher.match(uri)) {

PRODUCTS -> db.delete(PRODUCTS_TABLE_NAME, selection, selectionArgs)
PRODUCT_ID -> {
val id = uri.pathSegments[1]
db.delete(PRODUCTS_TABLE_NAME, "$ID = $id " +
if (!TextUtils.isEmpty(selection)) "AND
(
$selection)" else "", selectionArgs)
}
else -> throw IllegalArgumentException("Unknown URI: $uri")
}

context.contentResolver.notifyChange(uri, null)
return count
}

override fun update(uri: Uri, values: ContentValues, selection: String,
selectionArgs: Array<String>): Int {

val count = when(uriMatcher.match(uri)) {
PRODUCTS -> db.update(PRODUCTS_TABLE_NAME, values,
selection, selectionArgs)
PRODUCT_ID -> {
db.update(PRODUCTS_TABLE_NAME, values,
"$ID = ${uri.pathSegments[1]} " +
if (!TextUtils.isEmpty(selection)) " AND
(
$selection)" else "", selectionArgs)
}
else -> throw IllegalArgumentException("Unknown URI: $uri")
}

context.contentResolver.notifyChange(uri, null)
return count
}

override fun getType(uri: Uri): String {
//Returns the appropriate MIME type of records

return when (uriMatcher.match(uri)){
PRODUCTS -> "vnd.android.cursor.dir/vnd.example.products"
PRODUCT_ID -> "vnd.android.cursor.item/vnd.example.products"
else -> throw IllegalArgumentException("Unpermitted URI: " + uri)
}
}
}

Having added a suitable ProductProvider to provide content pertaining to saved products, we must register the new component in the AndroidManifest file. We have added the provider to the manifest file in the following code snippet:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.contentproviderexample">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider android:authorities="com.example.contentproviderexample
.ProductProvider" an
droid:name="ProductProvider"/>
</application>

</manifest>

Now, let's modify MainActivity to exploit this newly registered provider. Modify MainActivity.kt to contain the following content:

package com.example.contentproviderexample

import android.content.ContentValues
import android.net.Uri
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.text.TextUtils
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.Toast

class MainActivity : AppCompatActivity(), View.OnClickListener {

private lateinit var etProductName: EditText
private lateinit var etProductManufacturer: EditText
private lateinit var btnAddProduct: Button
private lateinit var btnShowProduct: Button

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bindViews()
setupInstances()
}

private fun bindViews() {
etProductName = findViewById(R.id.et_product_name)
etProductManufacturer = findViewById(R.id.et_product_manufacturer)
btnAddProduct = findViewById(R.id.btn_add_product)
btnShowProduct = findViewById(R.id.btn_show_products)
}

private fun setupInstances() {
btnAddProduct.setOnClickListener(this)
btnShowProduct.setOnClickListener(this)
supportActionBar?.hide()
}

private fun inputsValid(): Boolean {
var inputsValid = true
if (TextUtils.isEmpty(etProductName.text)) {
etProductName.error = "Field required."
etProductName.requestFocus()
inputsValid = false

} else if (TextUtils.isEmpty(etProductManufacturer.text)) {
etProductManufacturer.error = "Field required."
etProductManufacturer.requestFocus()
inputsValid = false
}

return inputsValid
}

private fun addProduct() {
val contentValues = ContentValues()

contentValues.put(ProductProvider.NAME, etProductName.text.toString())
contentValues.put(ProductProvider.MANUFACTURER,
etProductManufacturer.text.toString())
contentResolver.insert(ProductProvider.CONTENT_URI, contentValues)

showSaveSuccess()
}

The following function is called to show products that exist in the database:


private fun showProducts() {
val uri = Uri.parse(ProductProvider.URL)
val cursor = managedQuery(uri, null, null, null, "name")

if (cursor != null) {
if (cursor.moveToFirst()) {
do {
val res = "ID: ${cursor.getString(cursor.getColumnIndex
(ProductProvider.ID))}" + ",
PRODUCT NAME: ${cursor.getString(cursor.getColumnIndex
( ProductProvider.NAME))}" + ",
PRODUCT MANUFACTURER: ${cursor.getString(cursor.getColumnIndex
(ProductProvider.MANUFACTURER))}"

Toast.makeText(this, res, Toast.LENGTH_LONG).show()
} while (cursor.moveToNext())
}
} else {
Toast.makeText(this, "Oops, something went wrong.",
Toast.LENGTH_LONG).show()
}
}

private fun showSaveSuccess() {
Toast.makeText(this, "Product successfully saved.",
Toast.LENGTH_LONG).show()
}

override fun onClick(view: View) {
val id = view.id

if (id == R.id.btn_add_product) {
if (inputsValid()) {
addProduct()
}
} else if (id == R.id.btn_show_products) {
showProducts()
}
}
}

The two methods you should focus your attention on in the preceding code block are addProduct() and showProducts(). addProduct() stores the product data in a contentValues instance and then inserts this data into the SQLite database with the help of the ProductProvider by invoking contentResolver.insert(ProductProvider.CONTENT_URI, contentValues)showProducts() uses a Cursor to display the product information stored in the database in toast messages.

Now that we understand what's going on, let's run the application. Build and run the application as you have done thus far and wait until the application installs and starts. You will be taken straight to MainActivity and presented with a form to input the name and manufacturer of a product:

Upon inputting valid product information, click ADD PRODUCT. The product will be inserted as a new record in the products table of the application's SQLite database. Add a few more products with the form and click SHOW PRODUCTS:

Doing this will lead to the invocation of showProducts() in MainActivity. All product records will be fetched and displayed in toast messages one after the other. 

That is as much as we need to implement in a sample application to demonstrate how content providers work. Try to make the application even more awesome by implementing functionality for updating and deleting product records. Doing so will be good practice!

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

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