Android
|
Modified: |
Resources
http://developer.android.com/guide/topics/data/data-storage.html - Overview of Android data storage
http://www.javacodegeeks.com/2011/01/android-quick-preferences-tutorial.html - Preferences tutorial
http://mobile.tutsplus.com/tutorials/android/android-application-preferences/ - Preferences tutorial
http://www.vogella.de/articles/AndroidSQLite/article.html - SQLite tutorial
http://www.higherpass.com/Android/Tutorials/Creating-Lists-Using-The-Android-Listactivity/ - Content Provider Browser example
Overview
Persistent storage methods are examined:
- preferences - key-value pairs of primitive types.
- files - primitive data
- files - serialized objects
- database - SQLite
- content provider
Preferences
The SharedPreferences class provides a general framework that allows saving and retrieving persistent key-value pairs of primitive data types.
Use SharedPreferences to save any primitive data: booleans, floats, ints, longs, and strings. This data will persist across user sessions (even if the application is killed).
User PreferencesShared preferences are not strictly for saving "user preferences," such as what ringtone a user has chosen. To create user preferences, see PreferenceActivity, which provides an Activity framework to create user preferences, which will be automatically persisted (using shared preferences).
To get a SharedPreferences object for an application, use one of two methods:getSharedPreferences() - For multiple preferences files identified by name, specified with the first parameter.
getPreferences() - For one preferences file for Activity. As the only preferences file for Activity, a name is not needed.
Write values:
- Call edit() to get a SharedPreferences.Editor
- Add values with methods such as putBoolean() and putString().
- Commit the new values with commit()
Read values:
Use SharedPreferences methods such as getBoolean() and getString().
Example displaying last time app used and saving current time app stopped.
package edu.ius.rwisman.PreferencesExample;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.Locale;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.Toast;
public class AndroidPreferencesExample extends Activity {
public static final String PREFS_NAME = "MyPrefsFile";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
// Read a string value with key "LastUse"
String lastUse = settings.getString("LastUse", "");
Toast.makeText(this, "Last time: "+ lastUse,Toast.LENGTH_LONG).show();
}
@Override
protected void onStop(){
super.onStop();
SimpleDateFormat sdfDateTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
String currentTime = sdfDateTime.format(new Date(System.currentTimeMillis()));
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit(); // Editor object to make preference changes.
// Construct current date/time as String
// Write "LastUse" key with value
editor.putString("LastUse", currentTime);
// Commit
editor.commit();
Toast.makeText(this, "Current time: "+ currentTime,Toast.LENGTH_LONG).show();
}
} |
Android Developer Storage Discussion
Internal Storage - Store private data on the device memory.
External Storage - Store public data on the shared external storage.
SQLite Databases - Store structured data in a private database.
Internal storage example
The example:
- creates a file
- writes the current time to the file
- closes the file
- opens the file
- reads a String from the file
- close the files
package edu.ius.rwisman.AndroidFileExample;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.Locale;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.widget.Toast;
public class AndroidFileExample extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Construct current time/date String
SimpleDateFormat sdfDateTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
String currentTime = sdfDateTime.format(new Date(System.currentTimeMillis()));
String FILENAME = "TIME";
try {
DataOutputStream out = new DataOutputStream(openFileOutput(FILENAME, Context.MODE_PRIVATE));
// Write
out.writeUTF(currentTime);
out.close();
DataInputStream in = new DataInputStream(openFileInput(FILENAME));
// Read
String input = in.readUTF();
in.close();
Toast.makeText(this, "Read: "+ input,Toast.LENGTH_LONG).show();
} catch (Exception e) {}
}
} |
Serialized objects file I/O
Objects can be read/written to files after serialization.
Two methods are predefined for serialized objects:
- writeObject(object)
- readObject()
The example is similar to the one above, except that instead of writing and reading a String to a file, a Date object is written and read from a file.
package edu.ius.rwisman.AndroidFileExample;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.Locale;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.widget.Toast;
public class AndroidFileExample extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
SimpleDateFormat sdfDateTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
String FILENAME = "TIME";
try {
FileOutputStream fos = openFileOutput(FILENAME, Context.MODE_PRIVATE);
ObjectOutputStream os = new ObjectOutputStream(fos);
// Construct, serialize and write Date
os.writeObject(new Date(System.currentTimeMillis()));
os.close();
FileInputStream fis = openFileInput(FILENAME);
ObjectInputStream is = new ObjectInputStream(fis);
// Read object, cast as Date
Date date = (Date) is.readObject();
is.close();
// Convert Date object to String
Toast.makeText(this, "Read: "+ sdfDateTime.format( date ),Toast.LENGTH_LONG).show();
} catch (Exception e) {}
}
} |
Serialized your own objects file I/O
Objects can be read/written to files after serialization.
A class implementing Serializable can be written and read.
Two methods are predefined for serialized objects:
- writeObject(object)
- readObject()
The example is similar to the one above, except that instead of writing and reading a Date object, a Person object is written and read from a file.
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerializePerson {
public static void main(String[] args) {
String filename = "person.txt";
Person person = new Person("Ray", 62);
ObjectOutputStream out = null;
ObjectInputStream in = null;
try {
out = new ObjectOutputStream(new FileOutputStream("person.txt"));
out.writeObject(person);
out.close();
} catch (IOException ex) {
ex.printStackTrace();
}
try {
in = new ObjectInputStream(new FileInputStream(filename));
person = (Person) in.readObject();
in.close();
} catch (IOException ex) {
ex.printStackTrace();
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
System.out.println("Person: " + person.getName() + " " + person.getAge());
}
} |
Serialize Objects for Networking
From the above example, objects can be serialized for I/O, it makes sense that the I/O includes network connections.
Note that package and class definition must be the same on client and server.
The following Java example:
- client - connects to server
- server - accepts and sends a serialized Person object
- client - receives Person object.
package edu.ius.rwisman.AndroidSerializeClient;
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
|
package edu.ius.rwisman.AndroidSerializeClient;
import java.io.ObjectInputStream;
import java.net.Socket;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
public class AndroidSerializeClientActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new Thread(new Runnable() {
public void run() {
try {
Socket s = new Socket("192.168.1.75", 1990);
ObjectInputStream ois = new ObjectInputStream(
s.getInputStream());
Person p = (Person) ois.readObject();
ois.close();
s.close();
}
catch (Exception e) { Log.d("Exception", e+""); }
}
}).start();
}
}
package edu.ius.rwisman.AndroidSerializeClient;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class PersonServer {
public static void main(String args[]) throws Exception {
ServerSocket connection = new ServerSocket( 1990 );
Socket s = connection.accept();
ObjectOutputStream out = new ObjectOutputStream(s.getOutputStream( ));
out.writeObject(new Person("Ed", 21));
out.close();
s.close();
}
}
|
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="edu.ius.rwisman.AndroidSerializeClient"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="13" />
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".AndroidSerializeClientActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
|
SQLite
SQLite is an Open Source Database implemented on Android requiring little memory at runtime (approx. 250 KByte). SQLite supports standard relational database features like SQL syntax, transactions and prepared statements.
Android database I/O is relatively slow so it is recommended to perform this task in an AsyncTask or other threaded task.SQLite supports the data types:
- TEXT (similar to String in Java)
- INTEGER (similar to long in Java)
- REAL (similar to double in Java).
All other types must be converted into one of these fields before saving them in the database. SQLite itself does not validate if the types written to the columns are actually of the defined type, you can write an integer into a string column.
An application database is saved in the directory "DATA/data/APP_NAME/databases/FILENAME". "DATA" is the path which Environment.getDataDirectory() returns, "APP_NAME" is your application name and "FILENAME" is the name given the database during creation. Environment.getDataDirectory() usually return the SD card as location.
A SQLite database is private to the application which creates it. To share data with other applications, use a Content Provider.
SQLiteOpenHelperTo create and upgrade a database, subclass "SQLiteOpenHelper".
Override the methods, both receive an "SQLiteDatabase" object:
- onCreate() to create the database
- onUpgrade() to upgrade the database in case of changes in the database schema.
SQLiteOpenHelper provides the access methods to a "SQLiteDatabase" object which allows database access either in read or write mode:
- getReadableDatabase()
- getWriteableDatabase()
For the primary key of the database you should always use the identifier "_id" as some of Android functions rely on this standard.
SQLiteDatabase
"SQLiteDatabase" provides the methods:
- insert()
- update()
- delete()
- execSQL() method which allows to execute directly SQL.
The object "ContentValues" allow to define key/values for insert and update. The key is the column and the value is the value for this column.
Queries can be created via the method rawQuery() which accepts SQL or query() which provides an interface for specifying dynamic data or SQLiteQueryBuilder.
SQLiteBuilder is similar to the interface of an content provider therefore it is typically used in ContentProviders. A query returns always a "Cursor".
The method query has the parameters String dbName, int[] columnNames, String whereClause, String[] valuesForWhereClause, String[] groupBy, String[] having, String[] orderBy.To select all rows, pass "null" as the where clause. The where clause is specified without "where", for example "_id=19 and summary=?". If several values are required via ? you pass them in the valuesForWhereClause array to the query. In general if something is not required you can pass "null", e.g. for the group by clause.
Cursor
Cursor represents the result of a query.
getCount() returns the number of elements.
To move between individual data rows, use the methods moveToFirst() and moveToNext().
isAfterLast() checks at end of data.
SQL
Though SQLite methods are preferred, SQL can be executed for operations that do not return a record set (e.g. INSERT).
To insert record 4 into todo table of the open database would be:
database.execSQL("INSERT INTO todo VALUES(4, 'Play', 'Swim\nBike','Have fun')");
A method to construct the INSERT would build the string:
public void insertTodoSQL(long record, String category, String summary, String description) { String s = "INSERT INTO todo VALUES(" + record + ", '" + category + "','" + summary +"','" + description +"'); "; try { database.execSQL( s ); } catch(SQLException e) { } }Example - Todo list
TodoDB database consists of todo table.
todo table consists of rows with 4 columns with 3 rows inserted:
_id category summary description 1 Play Swim\nBike Have fun 2 Groceries Milk\nBread Buy groceries 3 Car Wash\nChange oil Service The example below consists of 3 classes:
- TodoDatabaseHelper - Creates and upgrades the todoDB database.
- TodoDbAdapter - Methods to:
- Open and close todoDB database.
- Insert, delete, update, and select rows of todo table.
- AndroidSQLiteActivity - Uses the TodoDbAdapter to:
- create the todoDB
- insert 3 rows of data
- select the first row record
- display the selected row values for column 1 (category) and column 2 (summary).
- clicking Next button moves through table rows to end.
package edu.ius.rwisman.AndroidSQLite;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class TodoDatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "todoDB";
private static final int DATABASE_VERSION = 1;
public TodoDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
// Called on database creation
@Override
public void onCreate(SQLiteDatabase database) {
database.execSQL(
"create table todo (_id integer primary key autoincrement, " +
"category text not null, summary text not null, description text not null);");
}
// Called on database upgrade, e.g. increasing the database version
@Override
public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) {
database.execSQL("DROP TABLE IF EXISTS todo");
onCreate(database);
}
}
|
package edu.ius.rwisman.AndroidSQLite;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
public class TodoDbAdapter {
// Database fields
public static final String KEY_ROWID = "_id";
public static final String KEY_CATEGORY = "category";
public static final String KEY_SUMMARY = "summary";
public static final String KEY_DESCRIPTION = "description";
private static final String DATABASE_TABLE = "todo";
private Context context;
private SQLiteDatabase database;
private TodoDatabaseHelper dbHelper;
public TodoDbAdapter(Context context) {
this.context = context;
}
public TodoDbAdapter open() throws SQLException {
dbHelper = new TodoDatabaseHelper(context);
database = dbHelper.getWritableDatabase();
return this;
}
public void close() {
dbHelper.close();
}
// Put String values into a Content object
private ContentValues createContentValues(String category, String summary, String description) {
ContentValues values = new ContentValues();
values.put(KEY_CATEGORY, category);
values.put(KEY_SUMMARY, summary);
values.put(KEY_DESCRIPTION, description);
return values;
}
// Create a new todo. If the todo is successfully created return the new
// rowId for that note, otherwise return a -1 to indicate failure.
public long insertTodo(String category, String summary, String description) {
ContentValues initialValues = createContentValues(category, summary, description);
return database.insert(DATABASE_TABLE, null, initialValues);
}
// Update the todo
public boolean updateTodo(long rowId, String category, String summary, String description) {
ContentValues updateValues = createContentValues(category, summary, description);
return database.update(DATABASE_TABLE, updateValues, KEY_ROWID + "=" + rowId, null) > 0;
}
// Deletes todo row
public boolean deleteTodo(long rowId) {
return database.delete(DATABASE_TABLE, KEY_ROWID + "=" + rowId, null) > 0;
}
// Return a Cursor over the list of all todo in the database
public Cursor selectAllTodos() {
return database.query(DATABASE_TABLE, new String[] { KEY_ROWID,
KEY_CATEGORY, KEY_SUMMARY, KEY_DESCRIPTION }, null, null, null, null, null);
}
// Return a Cursor positioned at the defined todo
public Cursor selectTodo(long rowId) throws SQLException {
Cursor mCursor = database.query(true, DATABASE_TABLE, new String[] {
KEY_ROWID, KEY_CATEGORY, KEY_SUMMARY, KEY_DESCRIPTION },
KEY_ROWID + "=" + rowId, null, null, null, null, null);
if (mCursor != null)
mCursor.moveToFirst();
return mCursor;
}
public Cursor selectTodoCategory(String selectCategory) throws SQLException {
Cursor mCursor = database.rawQuery(
"Select * from todo where category = ?", new String[] { selectCategory });
if (mCursor != null)
mCursor.moveToFirst();
return mCursor;
}
}
|

package edu.ius.rwisman.AndroidSQLite;
import android.database.Cursor;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.app.Activity;
public class AndroidSQLiteActivity extends Activity {
private TodoDbAdapter dbAdapter;
private Cursor cursor;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
dbAdapter = new TodoDbAdapter(this);
dbAdapter.open();
addData();
// cursor at "Car" record
cursor = dbAdapter.selectTodoCategory("Car");
cursor = dbAdapter.selectTodo(1); // cursor at Row 1
TextView categoryText = (TextView) findViewById(R.id.categoryText);
String s = cursor.getString(1); // Column 1
categoryText.setText(s);
TextView summaryText = (TextView) findViewById(R.id.summaryText);
s = cursor.getString(2); // Column 2
summaryText.setText(s);
cursor = dbAdapter.selectAllTodos(); // Select all rows
cursor.moveToFirst(); // cursor at Row 1
}
private void addData() {
// insertTodo( category, summary, description)
dbAdapter.insertTodo("Play", "Swim\nBike","Have fun");
dbAdapter.insertTodo("Groceries", "Milk\nBread","Buy groceries");
dbAdapter.insertTodo("Car", "Wash\nChange oil","Service");
}
public void nextOnClick(View v) {
if(cursor.isAfterLast()) return; // End Of Data
TextView categoryText = (TextView) findViewById(R.id.categoryText);
String s = cursor.getString(1);
categoryText.setText(s);
TextView summaryText = (TextView) findViewById(R.id.summaryText);
s = cursor.getString(2);
summaryText.setText(s);
cursor.moveToNext(); // cursor to next row
}
@Override
protected void onDestroy() {
super.onDestroy();
if (dbAdapter != null) {
dbAdapter.close();
}
}
}
|

_id category summary description 1 Play Swim\nBike Have fun 2 Groceries Milk\nBread Buy groceries 3 Car Wash\nChange oil Service
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<Button
android:id="@+id/next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Next"
android:onClick="nextOnClick"/>
<TableRow
android:id="@+id/tableRow1"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Category" />
</TableRow>
<TableRow
android:id="@+id/tableRow2"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/categoryText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView" />
</TableRow>
<TableRow
android:id="@+id/tableRow3"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="_____________" />
</TableRow>
<TableRow
android:id="@+id/tableRow4"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Summary" />
</TableRow>
<TableRow
android:id="@+id/tableRow5"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/summaryText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView" />
</TableRow>
</LinearLayout>
|
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="edu.ius.rwisman.AndroidSQLite"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="13" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".AndroidSQLiteActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
|
SimpleCursorAdapter
Cursor adapter maps columns from a cursor to TextViews or ImageViews defined in an XML file.
Specify which database columns to use for source data, which views to display the columns, and the XML file that defines the appearance of these views.
Two new XML files are used (main.xml is not):
- listview.xml - defines a ListView, necessary when using a ListActivity rather than Activity class.
- todoview.xml - defines the appearance of each row of the database.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<ListView
android:id="@android:id/android:list"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
|
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/category"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="28dip" />
<TextView
android:id="@+id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="28dip" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="28dip"
android:text="__________________________"/>
</LinearLayout>
|
The primary change is the use of a SimpleCursorAdapter that binds the database columns (TodoDbAdapter.KEY_CATEGORY and TodoDbAdapter.KEY_SUMMARY) to the todoview.xml elements (category and summary) for displaying each row entry.
String[] from = new String[] { TodoDbAdapter.KEY_CATEGORY , TodoDbAdapter.KEY_SUMMARY };
int[] to = new int[] { R.id.category, R.id.summary };
package edu.ius.rwisman.AndroidSQLite;
import android.database.Cursor;
import android.os.Bundle;
import android.view.View;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import android.app.ListActivity;
public class AndroidSQLiteActivity extends ListActivity {
private TodoDbAdapter dbAdapter;
private Cursor cursor;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.listview);
dbAdapter = new TodoDbAdapter(this);
dbAdapter.open();
addData(); // Adds to DB each execution
cursor = dbAdapter.selectAllTodos();
cursor.moveToFirst();
startManagingCursor(cursor);
SimpleCursorAdapter notes = new SimpleCursorAdapter(this, R.layout.todoview, cursor, from, to);
setListAdapter(notes);
}
private void addData() {
// insertTodo( category, summary, description)
dbAdapter.insertTodo("Play", "Swim\nBike","Have fun");
dbAdapter.insertTodo("Groceries", "Milk\nBread","Buy groceries");
dbAdapter.insertTodo("Car", "Wash\nChange oil","Service");
}
@Override
protected void onDestroy() {
super.onDestroy();
if (dbAdapter != null) {
dbAdapter.close();
}
}
}
|
Using SQL SELECT in rawQuery
SQL SELECT statement can be used to return the cursor to a selected recordset.
TodoDBAdapter contains an example method selecting for a specific category string.
public Cursor selectTodoCategory(String selectCategory) throws SQLException {
Cursor mCursor = database.rawQuery( "Select * from todo where category = ?", new String[] { selectCategory });
if (mCursor != null)
mCursor.moveToFirst();
return mCursor;
}The following returns a cursor to all selected records for category "Play":
cursor = dbAdapter.selectTodoCategory("Play");

Content Providers
Content providers store and retrieve data and make it accessible to all applications.
The only way to share data across applications; there's no common storage area that all Android packages can access.
Android ships with a number of content providers for common data types (audio, video, images, personal contact information, and so on), see listing in the android.provider package.Query these providers for the data contained (although, for some, must acquire the proper permission to read/write the data).
To make data public, there are two options:
- create your own content provider (a ContentProvider subclass)
- add the data to an existing provider — if there's one that controls the same type of data and write permission granted.
Example - Use existing content - Browser bookmarks
Content produced by one activity can be accessed by another activity.
The following example displays the current browser bookmarks.
Permission is required to access Browser content.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="edu.ius.rwisman.ContentProviderBrowser"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="13" />
<uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS" />
<uses-permission android:name="com.android.broswer.permission.WRITE_HISTORY_BOOKMARKS" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name="edu.ius.rwisman.ContentProviderBrowser.ContentProviderBrowserActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
|
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<TextView android:id="@android:id/empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Empty set"
/>
</LinearLayout>
|
The string array projection is populated with Android SDK constants for the database columns to retrieve.
The string array displayFields holds the Browser fields to display in the view. displayFields combined with the displayColumns integer array tells the SimpleCursorAdapter where to place the data from the cursor. displayFields and displayColumns are parallel arrays so order of values is important. The positions of each array relate what data gets placed into each view element. In this case, the bookmark title goes into the text1 field and the URL goes into the text2 field.
The cursor is created from managedQuery() that receives the URI of the database and the fields to retrieve.The SimpleCursorAdapter displays the data in a predefined android.R.layout.simple_list_item_2 built in to the Android SDK, having 2 internal TextViews, one with large text, followed by one with smaller text below. The cursor points to the retrieved data, displayFields the columns to display and displayViews the views in which to display.
package edu.ius.rwisman.ContentProviderBrowser;
import android.app.ListActivity;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.Browser;
import android.widget.SimpleCursorAdapter;
public class ContentProviderBrowserActivity extends ListActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
String[] projection = new String[] { // Define columns to retrieve
Browser.BookmarkColumns._ID,
Browser.BookmarkColumns.TITLE,
Browser.BookmarkColumns.URL};
Cursor cursor = managedQuery( // Retrieve projection from Browser URI
android.provider.Browser.BOOKMARKS_URI,
projection,
null, null, null);
// Define columns to display
String[] from = new String[] { Browser.BookmarkColumns.TITLE, Browser.BookmarkColumns.URL};
int[] to = new int[] { android.R.id.text1, android.R.id.text2 };
setListAdapter(new SimpleCursorAdapter( this,
android.R.layout.simple_list_item_2,
cursor,
from,
to));
}
}
|

Example - Creating your own content provider - TodoProvider
Download Producer and Provider
Todo SQLite database example above will be reused as a content provider, TodoProvider.
There are two Activity's, executed in the following order:
- ContentProviderProducer - the producer of the content provided, for creating and adding the records to the Todo database,
- ContentProviderConsumer - the consumer to the content provided, displaying the records.
TodoProvider is the ContentProvider, separate from the two activities.
ContentProviderProducer/TodoProvider
AndroidManifest.xml registers TodoProvider as a provider for authority with Uri: edu.ius.rwisman.TodoProvider
which is used by any consumer of the content.
No output is produced by ContentProviderProducerActivity or TodoProducer.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="edu.ius.rwisman.ContentProviderProducer"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="13" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".ContentProviderProducerActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider android:name="TodoProvider" android:authorities="edu.ius.rwisman.TodoProvider" />
</application>
</manifest>
|
Content provider TodoProvider.
Essential idea is that:
content://edu.ius.rwisman.TodoProvider/todos/2
results in TodoProvider execution, which returns a cursor referencing record 2 of the Todo database.
Not all content provider functions are fully implemented.
package edu.ius.rwisman.ContentProviderProducer;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
public class TodoProvider extends ContentProvider {
public static final String PROVIDER_NAME = "edu.ius.rwisman.TodoProvider";
private static final String DATABASE_TABLE = "todo";
private TodoDatabaseHelper dbHelper;
private static final int TODOS = 1;
private static final int TODO_ID = 2;
private static final UriMatcher uriMatcher;
static { // Uri known by TodoProvider
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PROVIDER_NAME, "todos", TODOS);
uriMatcher.addURI(PROVIDER_NAME, "todos/#", TODO_ID);
}
@Override
public boolean onCreate() { // opens todo database
dbHelper = new TodoDatabaseHelper(getContext());
return true;
}
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)){
case TODOS: // get all todos
return "vnd.android.cursor.dir/"+PROVIDER_NAME;
case TODO_ID: // get todo #
return "vnd.android.cursor.item/"+PROVIDER_NAME;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(DATABASE_TABLE);
if(uriMatcher.match(uri) == TODO_ID) // Append _id=2 to query for record 2
queryBuilder.appendWhere("_id=" + uri.getLastPathSegment());
Cursor cursor = queryBuilder.query( // Query database
dbHelper.getReadableDatabase(),
projection,
selection,
selectionArgs, null, null, sortOrder, null);
// Send notification to any observers
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
@Override
public Uri insert(Uri uri, ContentValues values) { return null; }
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; }
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; }
}
|
Create and add records to the Todo database. Nothing special.
package edu.ius.rwisman.ContentProviderProducer;
import android.app.Activity;
import android.os.Bundle;
public class ContentProviderProducerActivity extends Activity {
private TodoDbAdapter dbAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dbAdapter = new TodoDbAdapter(this);
dbAdapter.open();
addData(); // Adds to DB each execution
}
private void addData() {
dbAdapter.insertTodo("Play", "Swim\nBike","Have fun");
dbAdapter.insertTodo("Groceries", "Milk\nBread","Buy groceries");
dbAdapter.insertTodo("Car", "Wash\nChange oil","Service");
}
}
|
Nearly identical to earlier Browser example of content provider. Same main.xml file.
The string array projection is populated with Android SDK constants for the database columns to retrieve.
The string array displayFields holds the Todo fields to display in the view. displayFields combined with the displayColumns integer array tells the SimpleCursorAdapter where to place the data from the cursor. displayFields and displayColumns are parallel arrays so order of values is important. The positions of each array relate what data gets placed into each view element. In this case, the bookmark title goes into the text1 field and the URL goes into the text2 field.
The cursor is created from managedQuery() that receives the URI of the database and the fields to retrieve.The SimpleCursorAdapter displays the data in a predefined android.R.layout.simple_list_item_2 built in to the Android SDK, having 2 internal TextViews, one with large text, followed by one with smaller text below. The cursor points to the retrieved data, displayFields the columns to display and displayViews the views in which to display.
package edu.ius.rwisman.ContentProviderConsumer;
import android.app.ListActivity;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.widget.SimpleCursorAdapter;
public class ContentProviderConsumerActivity extends ListActivity {
public static final String KEY_ROWID = "_id";
public static final String KEY_CATEGORY = "category";
public static final String KEY_SUMMARY = "summary";
public static final String KEY_DESCRIPTION = "description";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
String[] projection = new String[] { KEY_ROWID,
KEY_CATEGORY,
KEY_SUMMARY
};
// Return cursor to record 2
Cursor cursor = managedQuery( Uri.parse("content://edu.ius.rwisman.TodoProvider/todos/2"),
projection, null, null, null);
String[] from = new String[] { KEY_CATEGORY, KEY_SUMMARY};
int[] to = new int[] { android.R.id.text1, android.R.id.text2 };
// Display query results
setListAdapter(new SimpleCursorAdapter( this,
android.R.layout.simple_list_item_2, cursor,
from,
to));
}
}
|
AndroidManifest.xml is nothing special.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="edu.ius.rwisman.ContentProviderConsumer"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="13" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".ContentProviderConsumerActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
|