The contentProvider must implement the following methods query(), insert(), update(), delete(), getType() and onCreate() to provide access to the underlying data. If you don't implement the delete method just send an exception to let the application know or if you don't get an URI match. If you have done SQLite database access using the database helper you will find there is not much difference here. All you have to do it create a provider that matches URIs and then figures out what to do with the request.
In this example we will create a simple maintenance program to maintain a list of countries. The access to the country database will be thru the Content Provider that will implement all the CRUD functions. On the screen we will display an existing list of countries and let the user add, update and delete a country. Here is the step by step process to implement the above mentioned functionality using the Content Provider.
Step 1: Create the SQLite database Object - CountriesDb.java
package com.as400samplecode; import android.database.sqlite.SQLiteDatabase; import android.util.Log; public class CountriesDb { public static final String KEY_ROWID = "_id"; public static final String KEY_CODE = "code"; public static final String KEY_NAME = "name"; public static final String KEY_CONTINENT = "continent"; private static final String LOG_TAG = "CountriesDb"; public static final String SQLITE_TABLE = "Country"; private static final String DATABASE_CREATE = "CREATE TABLE if not exists " + SQLITE_TABLE + " (" + KEY_ROWID + " integer PRIMARY KEY autoincrement," + KEY_CODE + "," + KEY_NAME + "," + KEY_CONTINENT + "," + " UNIQUE (" + KEY_CODE +"));"; public static void onCreate(SQLiteDatabase db) { Log.w(LOG_TAG, DATABASE_CREATE); db.execSQL(DATABASE_CREATE); } public static void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w(LOG_TAG, "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data"); db.execSQL("DROP TABLE IF EXISTS " + SQLITE_TABLE); onCreate(db); } }
Step 2: Create the SQLite database Helper - MyDatabaseHelper.java
package com.as400samplecode; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class MyDatabaseHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "TheWorld"; private static final int DATABASE_VERSION = 1; MyDatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { CountriesDb.onCreate(db); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { CountriesDb.onUpgrade(db, oldVersion, newVersion); } }
Step 3: Create the ContentProvider - MyContentProvider.java
package com.as400samplecode; import android.content.ContentProvider; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.text.TextUtils; public class MyContentProvider extends ContentProvider{ private MyDatabaseHelper dbHelper; private static final int ALL_COUNTRIES = 1; private static final int SINGLE_COUNTRY = 2; // authority is the symbolic name of your provider // To avoid conflicts with other providers, you should use // Internet domain ownership (in reverse) as the basis of your provider authority. private static final String AUTHORITY = "com.as400samplecode.contentprovider"; // create content URIs from the authority by appending path to database table public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/countries"); // a content URI pattern matches content URIs using wildcard characters: // *: Matches a string of any valid characters of any length. // #: Matches a string of numeric characters of any length. private static final UriMatcher uriMatcher; static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(AUTHORITY, "countries", ALL_COUNTRIES); uriMatcher.addURI(AUTHORITY, "countries/#", SINGLE_COUNTRY); } // system calls onCreate() when it starts up the provider. @Override public boolean onCreate() { // get access to the database helper dbHelper = new MyDatabaseHelper(getContext()); return false; } //Return the MIME type corresponding to a content URI @Override public String getType(Uri uri) { switch (uriMatcher.match(uri)) { case ALL_COUNTRIES: return "vnd.android.cursor.dir/vnd.com.as400samplecode.contentprovider.countries"; case SINGLE_COUNTRY: return "vnd.android.cursor.item/vnd.com.as400samplecode.contentprovider.countries"; default: throw new IllegalArgumentException("Unsupported URI: " + uri); } } // The insert() method adds a new row to the appropriate table, using the values // in the ContentValues argument. If a column name is not in the ContentValues argument, // you may want to provide a default value for it either in your provider code or in // your database schema. @Override public Uri insert(Uri uri, ContentValues values) { SQLiteDatabase db = dbHelper.getWritableDatabase(); switch (uriMatcher.match(uri)) { case ALL_COUNTRIES: //do nothing break; default: throw new IllegalArgumentException("Unsupported URI: " + uri); } long id = db.insert(CountriesDb.SQLITE_TABLE, null, values); getContext().getContentResolver().notifyChange(uri, null); return Uri.parse(CONTENT_URI + "/" + id); } // The query() method must return a Cursor object, or if it fails, // throw an Exception. If you are using an SQLite database as your data storage, // you can simply return the Cursor returned by one of the query() methods of the // SQLiteDatabase class. If the query does not match any rows, you should return a // Cursor instance whose getCount() method returns 0. You should return null only // if an internal error occurred during the query process. @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteDatabase db = dbHelper.getWritableDatabase(); SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); queryBuilder.setTables(CountriesDb.SQLITE_TABLE); switch (uriMatcher.match(uri)) { case ALL_COUNTRIES: //do nothing break; case SINGLE_COUNTRY: String id = uri.getPathSegments().get(1); queryBuilder.appendWhere(CountriesDb.KEY_ROWID + "=" + id); break; default: throw new IllegalArgumentException("Unsupported URI: " + uri); } Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); return cursor; } // The delete() method deletes rows based on the seletion or if an id is // provided then it deleted a single row. The methods returns the numbers // of records delete from the database. If you choose not to delete the data // physically then just update a flag here. @Override public int delete(Uri uri, String selection, String[] selectionArgs) { SQLiteDatabase db = dbHelper.getWritableDatabase(); switch (uriMatcher.match(uri)) { case ALL_COUNTRIES: //do nothing break; case SINGLE_COUNTRY: String id = uri.getPathSegments().get(1); selection = CountriesDb.KEY_ROWID + "=" + id + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); break; default: throw new IllegalArgumentException("Unsupported URI: " + uri); } int deleteCount = db.delete(CountriesDb.SQLITE_TABLE, selection, selectionArgs); getContext().getContentResolver().notifyChange(uri, null); return deleteCount; } // The update method() is same as delete() which updates multiple rows // based on the selection or a single row if the row id is provided. The // update method returns the number of updated rows. @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { SQLiteDatabase db = dbHelper.getWritableDatabase(); switch (uriMatcher.match(uri)) { case ALL_COUNTRIES: //do nothing break; case SINGLE_COUNTRY: String id = uri.getPathSegments().get(1); selection = CountriesDb.KEY_ROWID + "=" + id + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); break; default: throw new IllegalArgumentException("Unsupported URI: " + uri); } int updateCount = db.update(CountriesDb.SQLITE_TABLE, values, selection, selectionArgs); getContext().getContentResolver().notifyChange(uri, null); return updateCount; } }
Step 4: Row Layout for Country Display - country_info.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="6dip"> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:text="Country:" android:textAppearance="?android:attr/textAppearanceMedium" /> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_toRightOf="@+id/textView1" android:text="(" android:textAppearance="?android:attr/textAppearanceMedium" /> <TextView android:id="@+id/code" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_toRightOf="@+id/textView2" android:text="Medium Text" android:textAppearance="?android:attr/textAppearanceMedium" /> <TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_toRightOf="@+id/code" android:text=")" android:textAppearance="?android:attr/textAppearanceMedium" /> <TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_toRightOf="@+id/textView3" android:text="Medium Text" android:textAppearance="?android:attr/textAppearanceMedium" /> <TextView android:id="@+id/textView4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/textView1" android:text="Continent:" android:textAppearance="?android:attr/textAppearanceMedium" /> <TextView android:id="@+id/continent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/textView4" android:layout_alignBottom="@+id/textView4" android:layout_toRightOf="@+id/textView4" android:text="Medium Text" android:textAppearance="?android:attr/textAppearanceMedium" /> </RelativeLayout>
Step 5: Main Layout with ListView - activity_main.xml
<?xml version="1.0" encoding="UTF-8"?> <RelativeLayout 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"> <Button android:id="@+id/add" style="?android:attr/buttonStyleSmall" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:text="@string/add_country" /> <ListView android:id="@+id/countryList" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/add" /> </RelativeLayout>
Step 6: Main Activity using LoaderManager for Country List - MainActivity.java
The list of countries is displayed in a ListView using the CursorLoader that queries the ContentResolver and returns a Cursor. This class implements the Loader protocol in a standard way for querying cursors, building on AsyncTaskLoader to perform the cursor query on a background thread so that it does not block the application's UI. After the loader has finished its loading just swap the new cursor from the Content Provider and return the old Cursor.package com.as400samplecode; import android.os.Bundle; import android.app.Activity; import android.content.CursorLoader; import android.content.Intent; import android.content.Loader; import android.database.Cursor; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.Button; import android.widget.ListView; import android.widget.SimpleCursorAdapter; import android.widget.Toast; import android.app.LoaderManager; public class MainActivity extends Activity implements LoaderManager.LoaderCallbacks<Cursor>{ private SimpleCursorAdapter dataAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); displayListView(); Button add = (Button) findViewById(R.id.add); add.setOnClickListener(new OnClickListener() { public void onClick(View v) { // starts a new Intent to add a Country Intent countryEdit = new Intent(getBaseContext(), CountryEdit.class); Bundle bundle = new Bundle(); bundle.putString("mode", "add"); countryEdit.putExtras(bundle); startActivity(countryEdit); } }); } @Override protected void onResume() { super.onResume(); //Starts a new or restarts an existing Loader in this manager getLoaderManager().restartLoader(0, null, this); } private void displayListView() { // The desired columns to be bound String[] columns = new String[] { CountriesDb.KEY_CODE, CountriesDb.KEY_NAME, CountriesDb.KEY_CONTINENT }; // the XML defined views which the data will be bound to int[] to = new int[] { R.id.code, R.id.name, R.id.continent, }; // create an adapter from the SimpleCursorAdapter dataAdapter = new SimpleCursorAdapter( this, R.layout.country_info, null, columns, to, 0); // get reference to the ListView ListView listView = (ListView) findViewById(R.id.countryList); // Assign adapter to ListView listView.setAdapter(dataAdapter); //Ensures a loader is initialized and active. getLoaderManager().initLoader(0, null, this); listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> listView, View view, int position, long id) { // Get the cursor, positioned to the corresponding row in the result set Cursor cursor = (Cursor) listView.getItemAtPosition(position); // display the selected country String countryCode = cursor.getString(cursor.getColumnIndexOrThrow(CountriesDb.KEY_CODE)); Toast.makeText(getApplicationContext(), countryCode, Toast.LENGTH_SHORT).show(); String rowId = cursor.getString(cursor.getColumnIndexOrThrow(CountriesDb.KEY_ROWID)); // starts a new Intent to update/delete a Country // pass in row Id to create the Content URI for a single row Intent countryEdit = new Intent(getBaseContext(), CountryEdit.class); Bundle bundle = new Bundle(); bundle.putString("mode", "update"); bundle.putString("rowId", rowId); countryEdit.putExtras(bundle); startActivity(countryEdit); } }); } // This is called when a new Loader needs to be created. @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { String[] projection = { CountriesDb.KEY_ROWID, CountriesDb.KEY_CODE, CountriesDb.KEY_NAME, CountriesDb.KEY_CONTINENT}; CursorLoader cursorLoader = new CursorLoader(this, MyContentProvider.CONTENT_URI, projection, null, null, null); return cursorLoader; } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) dataAdapter.swapCursor(data); } @Override public void onLoaderReset(Loader<Cursor> loader) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. dataAdapter.swapCursor(null); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_main, menu); return true; } }
Step 7: Detail Layout for Insert,Update and Delete - detail_page.xml
<?xml version="1.0" encoding="UTF-8"?> <RelativeLayout 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:paddingLeft="10dp"> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:text="@string/country_info" android:textAppearance="?android:attr/textAppearanceMedium" android:textStyle="bold" /> <TextView android:id="@+id/choose_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/textView1" android:text="@string/choose_text" android:textAppearance="?android:attr/textAppearanceMedium" android:layout_marginTop="10dp" /> <Spinner android:id="@+id/continentList" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/textView1" android:layout_marginTop="10dp" android:layout_toRightOf="@id/choose_text" /> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/continentList" android:text="@string/country_code" android:textAppearance="?android:attr/textAppearanceMedium" android:layout_marginTop="10dp" /> <TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/textView2" android:layout_marginTop="13dp" android:text="@string/country_name" android:textAppearance="?android:attr/textAppearanceMedium" /> <EditText android:id="@+id/code" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBottom="@+id/textView2" android:layout_alignLeft="@+id/name" android:layout_toRightOf="@+id/textView2" android:ems="3" android:maxLength="3" android:inputType="textCapCharacters" android:paddingLeft="10dp"> <requestFocus /> </EditText> <EditText android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBottom="@+id/textView3" android:layout_toRightOf="@+id/textView3" android:ems="30" android:inputType="text" android:paddingLeft="10dp" /> <Button android:id="@+id/save" style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_below="@+id/name" android:layout_marginRight="21dp" android:layout_marginTop="13dp" android:text="@string/save_country" /> <Button android:id="@+id/delete" style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/save" android:layout_alignBottom="@+id/save" android:layout_toLeftOf="@+id/save" android:text="@string/del_country" /> </RelativeLayout>
Step 8: Detail Activity implementing CRUD operations - CountryEdit.java
package com.as400samplecode; import android.app.Activity; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.Spinner; import android.widget.Toast; public class CountryEdit extends Activity implements OnClickListener{ private Spinner continentList; private Button save, delete; private String mode; private EditText code, name; private String id; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.detail_page); // get the values passed to the activity from the calling activity // determine the mode - add, update or delete if (this.getIntent().getExtras() != null){ Bundle bundle = this.getIntent().getExtras(); mode = bundle.getString("mode"); } // get references to the buttons and attach listeners save = (Button) findViewById(R.id.save); save.setOnClickListener(this); delete = (Button) findViewById(R.id.delete); delete.setOnClickListener(this); code = (EditText) findViewById(R.id.code); name = (EditText) findViewById(R.id.name); // create a dropdown for users to select various continents continentList = (Spinner) findViewById(R.id.continentList); ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.continent_array, android.R.layout.simple_spinner_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); continentList.setAdapter(adapter); // if in add mode disable the delete option if(mode.trim().equalsIgnoreCase("add")){ delete.setEnabled(false); } // get the rowId for the specific country else{ Bundle bundle = this.getIntent().getExtras(); id = bundle.getString("rowId"); loadCountryInfo(); } } public void onClick(View v) { // get values from the spinner and the input text fields String myContinent = continentList.getSelectedItem().toString(); String myCode = code.getText().toString(); String myName = name.getText().toString(); // check for blanks if(myCode.trim().equalsIgnoreCase("")){ Toast.makeText(getBaseContext(), "Please ENTER country code", Toast.LENGTH_LONG).show(); return; } // check for blanks if(myName.trim().equalsIgnoreCase("")){ Toast.makeText(getBaseContext(), "Please ENTER country name", Toast.LENGTH_LONG).show(); return; } switch (v.getId()) { case R.id.save: ContentValues values = new ContentValues(); values.put(CountriesDb.KEY_CODE, myCode); values.put(CountriesDb.KEY_NAME, myName); values.put(CountriesDb.KEY_CONTINENT, myContinent); // insert a record if(mode.trim().equalsIgnoreCase("add")){ getContentResolver().insert(MyContentProvider.CONTENT_URI, values); } // update a record else { Uri uri = Uri.parse(MyContentProvider.CONTENT_URI + "/" + id); getContentResolver().update(uri, values, null, null); } finish(); break; case R.id.delete: // delete a record Uri uri = Uri.parse(MyContentProvider.CONTENT_URI + "/" + id); getContentResolver().delete(uri, null, null); finish(); break; // More buttons go here (if any) ... } } // based on the rowId get all information from the Content Provider // about that country private void loadCountryInfo(){ String[] projection = { CountriesDb.KEY_ROWID, CountriesDb.KEY_CODE, CountriesDb.KEY_NAME, CountriesDb.KEY_CONTINENT}; Uri uri = Uri.parse(MyContentProvider.CONTENT_URI + "/" + id); Cursor cursor = getContentResolver().query(uri, projection, null, null, null); if (cursor != null) { cursor.moveToFirst(); String myCode = cursor.getString(cursor.getColumnIndexOrThrow(CountriesDb.KEY_CODE)); String myName = cursor.getString(cursor.getColumnIndexOrThrow(CountriesDb.KEY_NAME)); String myContinent = cursor.getString(cursor.getColumnIndexOrThrow(CountriesDb.KEY_CONTINENT)); code.setText(myCode); name.setText(myName); continentList.setSelection(getIndex(continentList, myContinent)); } } // this sets the spinner selection based on the value private int getIndex(Spinner spinner, String myString){ int index = 0; for (int i=0;i<spinner.getCount();i++){ if (spinner.getItemAtPosition(i).equals(myString)){ index = i; } } return index; } }
Step 9: Define Strings and Arrays - strings.xml
<resources> <string name="app_name">SQLite Database Content Provider</string> <string name="menu_settings">Settings</string> <string name="add_country">Add a new Country</string> <string name="country_info">Country Information</string> <string name="country_code">Code</string> <string name="country_name">Name</string> <string name="save_country">Save</string> <string name="del_country">Delete</string> <string name="choose_text">Choose Continent</string> <string-array name="continent_array"> <item>Africa</item> <item>Antartica</item> <item>Asia</item> <item>Europe</item> <item>North America</item> <item>Oceania</item> <item>South America</item> </string-array> </resources>
Step 10: Define Android Manifest file
<?xml version="1.0" encoding="UTF-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.as400samplecode" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="15" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".CountryEdit" android:label="@string/app_name" /> <provider android:name=".MyContentProvider" android:exported="true" android:readPermission="true" android:writePermission="true" android:authorities="com.as400samplecode.contentprovider" /> </application> </manifest>
No comments:
Post a Comment
NO JUNK, Please try to keep this clean and related to the topic at hand.
Comments are for users to ask questions, collaborate or improve on existing.