Data Storage: Part 3 (SQLite)
SQLite SQLite is a software library that provides full relational database capability for Android applications. Each application that uses SQLite has its own instance of the database, which is by default accessible only from the application itself. The database is stored in the following folder: /data/data/<package name>/databases A Content Provider can be used to share the database information with other applications. ©SoftMoore Consulting
Features of SQLite ACID transactions (atomic, consistent, isolated, and durable) Zero-configuration – no setup or administration. Implements most of SQL92. Complete database is stored in a single file. Supports terabyte-sized databases and gigabyte-sized strings and blobs. Small code footprint (less than 325KB fully configured). Self-contained: no external dependencies. C source code is in the public domain. ©SoftMoore Consulting
SQLite Datatypes Most SQL database systems use static typing; i.e., the type of a value is determined by the column in which the value is stored. SQLite uses a more general dynamic type system – the type of a value is associated with the value itself, not with its column. Think of column types as hints. It is possible to store a string in an integer column and vice versa. ©SoftMoore Consulting
SQLite Datatypes (continued) Each value stored in an SQLite database has one of the following storage classes: NULL. The value is a NULL value. INTEGER. The value is a signed integer, stored in 1, 2, 3, 4, 6, or 8 bytes depending on the magnitude of the value. REAL. The value is a floating point value, stored as an 8-byte IEEE floating point number. TEXT. The value is a text string, stored using the database encoding (UTF-8, UTF-16BE or UTF-16LE). BLOB. The value is a blob of data, stored exactly as it was input. Note that a storage class is slightly more general than a type. ©SoftMoore Consulting
SQLite Datatypes (continued) Each column in a SQLite database is assigned one of the following type affinities: TEXT – NUMERIC INTEGER – REAL NONE SQLite does not have a separate Boolean storage class. Boolean values are stored as integers 0 (false) and 1 (true). SQLite does not have a storage class set aside for storing dates and/or times. Instead, the built-in date and time functions of SQLite are capable of storing dates and times as TEXT, REAL, or INTEGER values. ©SoftMoore Consulting
Using the sqlite3 Command-Line In addition to accessing a SQLite database from an Android application, it is also possible to interact with the database on a virtual device using the sqlite3 command-line tool via the Android Debug Bridge (ADB) remote shell. Preparation add an environment variable ANDROID_SDK_HOME; e.g., ANDROID_SDK_HOME=C:\Java\android-sdk-windows add the following to your PATH environment variable: %ANDROID_SDK_HOME% %ANDROID_SDK_HOME%\tools %ANDROID_SDK_HOME%\platform-tools ©SoftMoore Consulting
Using the sqlite3 Command-Line (continued) Start a virtual device (emulator) and launch the ADB shell from a command prompt C:\>adb shell Connect to the database # sqlite3 /data/data/edu.citadel.android.emergency/databases/emergency.db sqlite3 /data/data/edu.citadel.android.emergency/databases/emergency.db SQLite version 3.6.22 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> Enter SQL statements and SQLite commands at the prompt. SQL statements must be terminated by a semicolon. SQLite commands start with a period. SQL statements and SQLite commands are echoed Note: Does not work with a real device. response ©SoftMoore Consulting
Selected SQLite Commands .databases List names/files of attached databases .exit Exit this program .header(s) ON|OFF Turn display of headers on or off .help Show all SQLite commands .import FILE TABLE Import data from FILE into TABLE .mode column Left-aligned columns. (See .width) .read FILENAME Execute SQL in FILENAME .schema Show the CREATE statements .width NUM NUM … Set column widths for “column” mode ©SoftMoore Consulting
Example: Using the sqlite3 Command-Line sqlite> .mode column .mode column sqlite> .width 3 15 12 .width 3 15 12 sqlite> .headers ON .headers ON sqlite> select * from emergency_contacts; select * from emergency_contacts; _id name phone_num --- --------------- ------------ 1 Emergency - 911 911 2 Home 843-123-4567 3 Kayran - Cell 843-234-5678 4 John - Cell 847-345-6789 5 Robert - Cell 202-456-7890 6 Angela - Cell 434-567-8901 sqlite> ©SoftMoore Consulting
Developing Applications Using SQLite Primary classes and interfaces android.database.sqlite.SQLiteDatabase android.database.sqlite.SQLiteOpenHelper (abstract) android.database.Cursor (interface) Using adapters to connect data and views Understanding relational databases and SQL ©SoftMoore Consulting
Class SQLiteOpenHelper Class SQLiteOpenHelper is an abstract helper class in package android.database.sqlite that can be used to manage database creation and versioning. Class SQLiteOpenHelper will open the database if it exists create the database if it does not exist upgrade the database as necessary To use class SQLiteOpenHelper, create a subclass that overrides abstract methods onCreate(), onUpgrade(). Note: Database names must be unique within an application, not across all applications. ©SoftMoore Consulting
Selected Methods in Class SQLiteOpenHelper SQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) constructor to initialize a SQLiteOpenHelper object the factory parameter can be null for the default value void close() close an open database SQLiteDatabase getReadableDatabase() create and/or open a database for reading only SQLiteDatabase getWritableDatabase() create and/or open a database for both reading and writing ©SoftMoore Consulting
Selected Methods in Class SQLiteOpenHelper (continued) abstract void onCreate(SQLiteDatabase db) called when the database is created for the first time Note: You can “preload” a set of database values in this method. void onOpen(SQLiteDatabase db) called when the database is opened abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) called when the database needs to be upgraded ©SoftMoore Consulting
Contract Class Applications that use a database and/or implement a content provider (see next section) often implement a “contract” class. A contract class defines constants that help applications work with the content URIs, column names, intent actions, and other features of a content provider. Contract classes are not included automatically with a provider; the provider’s developer has to define them and make them available to other developers. ©SoftMoore Consulting
Example: Contract Class public final class EmergencyContract { public static final String DB_NAME = "emergency.db"; public static final int DB_VERSION = 1; public static final String TABLE_NAME = "emergency_contacts"; public static final String[] COLUMNS = { "_id", "name", "phone_num" }; public static final String AUTHORITY = "edu.citadel.android.emergency"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME); private EmergencyContract() {} // prevent instantiation } Useful for adding a content provider. ©SoftMoore Consulting
Example: Using SQLiteOpenHelper public class EmergencyDbOpenHelper extends SQLiteOpenHelper { /** * Construct a SQLiteOpenHelper object for the * emergency database. */ public EmergencyDbOpenHelper(Context context) super(context, EmergencyContract.DB_NAME, null, EmergencyContract.DB_VERSION); } (continued on next slide) ©SoftMoore Consulting
Example: Using SQLiteOpenHelper (continued) @Override public void onCreate(SQLiteDatabase db) { String createSql = "create table " + EmergencyContract.TABLE_NAME + "(" + " _id integer primary key autoincrement," + " name text not null," + " phone_num text not null" + ")"; db.execSQL(createSql); insertContacts(db); } (continued on next slide) ©SoftMoore Consulting
Example: Using SQLiteOpenHelper (continued) @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // This is version 1 so no actions are required. // Possible actions include dropping/recreating // tables, saving/restoring data in tables, etc. } private void insertContacts(SQLiteDatabase db) // perform inserts to initialize the database ©SoftMoore Consulting
Class SQLiteDatabase Class SQLiteDatabase (in package android.database.sqlite) exposes methods to manage a SQLite database. Class SQLiteDatabase has methods to create, delete, execute SQL commands, and to perform other common database management tasks. Class SQLiteDatabase has several “convenience” methods for constructing SQL queries, inserts, etc. Alternatively, other methods can be used to directly execute SQL commands passed as string parameters. ©SoftMoore Consulting
Selected Methods in Class SQLiteDatabase void beginTransaction() Begins a transaction. int delete(String table, String whereClause, String[] whereArgs) Convenience method for deleting rows in the database. void endTransaction() End a transaction. void execSQL(String sql) Execute a single SQL statement that does not return data. long insert(String table, String nullColumnHack, ContentValues values) Convenience method for inserting a row into the database. ©SoftMoore Consulting
Selected Methods in Class SQLiteDatabase (continued) Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) Query the given table, returning a Cursor over the result set. Cursor rawQuery(String sql, String[] selectionArgs) Runs the provided SQL and returns a Cursor over the result set. int update(String table, ContentValues values, String whereClause, String[] whereArgs) Convenience method for updating rows in the database. ©SoftMoore Consulting
Example: Using Query Methods in Class SQLiteDatabase SQLiteOpenHelper openHelper = new EmergencyDbOpenHelper(this); SQLiteDatabase db = openHelper.getReadableDatabase(); Query using convenience methods String tableName = "emergency_contacts"; String[] columns = {"name", "phone_num"}; String orderBy = "name"; Cursor cursor = db.query(tableName, columns, null, null, null, null, orderBy); Query using method rawQuery() String query = "select name, phone_num" + " from emergency_contacts" + " order by name"; Cursor cursor = db.rawQuery(query, null); ©SoftMoore Consulting
Example: Using Insert Methods in Class SQLiteDatabase SQLiteOpenHelper openHelper = new EmergencyDbOpenHelper(this); SQLiteDatabase db = openHelper.getWritableDatabase(); Insert using convenience methods String tableName = "emergency_contacts"; ContentValues values = new ContentValues(); values.put("name", "Emergency – 911"); values.put("phone_num", "911"); db.insert(tableName, null, values); Insert using method execSQL() String insertSql = "insert into emergency_contacts" + " (name, phone_num) values" + " (\"Emergency - 911\", \"911\")"; db.execSQL(insertSql); ©SoftMoore Consulting
Interface Cursor Interface Cursor (in package android.database) is used to iterate over the rows returned by a “select” query. analogous to a Java Iterator or a JDBC ResultSet Interface Cursor provides random read-write access to the result set returned by a database query. Methods in interface Cursor can be used to position the cursor within the result set (“move” methods) to retrieve column values from the row (“get” methods) to provide metadata about the query (e.g., getColumnNames()). ©SoftMoore Consulting
Selected Methods in Interface Cursor int getColumnIndex(String columnName) Returns zero-based index for the given column name double getDouble(int columnIndex) int getInt(int columnIndex) long getLong(int columnIndex) String getString(int columnIndex) boolean move(int offset) boolean moveToFirst() boolean moveToNext() boolean moveToPosition(int position) Returns the value of the requested column as the specified type. Moves the cursor as specified. ©SoftMoore Consulting
Using a Cursor Similar to using a JDBC ResultSet, initially a cursor is positioned before the first row. The various “move” methods can be used to move the cursor to a row. The moveToNext() method moves the cursor to the next row. Since it returns false when there are no more rows, it can be used in a while loop to iterate through the rows of a cursor. Example while (cursor.moveToNext()) { ... // retrieve/process data for one row } ©SoftMoore Consulting
Example: Using Interface Cursor SQLiteDatabase db = openHelper.getReadableDatabase(); String query = "select * from emergency_contacts"; Cursor cursor = db.rawQuery(query, null); while (cursor.moveToNext()) { int id = cursor.getInt(0); String name = cursor.getString(1); String phoneNum = cursor.getString(2); String message = "ID: " + id + ", Name: " + name + ", Phone Number: " + phoneNum; Toast toast = Toast.makeText(Emergency.this, message, Toast.LENGTH_SHORT); toast.show(); } ©SoftMoore Consulting
Data Binding The binding of data to views is simplified by using a few specialized classes and interfaces. Class ListActivity can be used to display a list of items by binding to a data source such as an array or Cursor. Class AdapterView is a view group whose children are determined by an Adapter. Commonly used subclasses include ListView and Spinner. Interface Adapter acts as a bridge between an AdapterView and the underlying data for that view. Commonly used classes that extend Adapter include ArrayAdapter<T> and CursorAdapter. ©SoftMoore Consulting
Data Binding (continued) ListActivity (an activity) AdapterView (a view group) Adapter data source ©SoftMoore Consulting
Class ListActivity Class ListActivity is a subclass of Activity used to display a list of items by binding to a data source such as an array or Cursor. It also exposes event handlers when the user selects an item. A ListActivity uses two layouts, a default layout for the screen and a row layout for the individual rows in the list. The default layout contains a ListView object with the id “@android:id/list” controlling the overall display of the list, and it can optionally contain another view with an id of "@android:id/empty" to display when the list is empty. ©SoftMoore Consulting
Selected Methods in Class ListActivity ListAdapter getListAdapter() Get the ListAdapter associated with this activity’s ListView. ListView getListView() Get the activity’s list view widget. void setListAdapter(ListAdapter adapter) Set the ListAdapter for the list view. void onListItemClick(ListView l, View v, int position, long id) Called when an item in the list is selected. (Equivalent to implementing an OnItemClickListener for the list view − see next slide.) ©SoftMoore Consulting
Selected Methods in Class ListActivity (continued) Implementing method onListItemClick() is equivalent to implementing an OnItemClickListener for the list view. ListView lv = getListView(); lv.setOnItemClickListener(new AdapterView.OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) ... } }); The above approach must be used if you want to implement a method to respond to long clicks; i.e., you must implement OnItemLongClickListener() for the list view. ©SoftMoore Consulting
Using Class ListActivity Extend class ListActivity (rather than Activity) public class MainActivity extends ListActivity { ... } Define the default layout for the screen. Define the row layout for the individual rows. ©SoftMoore Consulting
Example: Default Layout for ListActivity <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@android:id/list" android:layout_height="wrap_content" android:fastScrollEnabled="true" android:scrollbarSize="@dimen/scrollbarSize" android:divider="@color/dividerColor" android:dividerHeight="@dimen/dividerHeight" android:layout_weight="1.0"/> Note the id! No “+” sign tells Android to use an existing id and not to generate a new one.) (continued on next slide) ©SoftMoore Consulting
Example: Default Layout for ListActivity (continued) <TextView android:id="@android:id/empty" android:layout_width="match_parent" android:layout_height="wrap_content" android:textStyle="bold" android:textSize=“@dimen/emptyTextSize" android:text="@string/emptyList" /> ... other (e.g., a button) </LinearLayout> Note the id! ©SoftMoore Consulting
Example: Layout for each Row in the ListActivity <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:padding=" @dimen/contactPadding "> <TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textStyle="bold" android:textSize=" @dimen/nameTextSize " /> (continued on next slide) ©SoftMoore Consulting
Example: Row Layout for Each Row in the ListActivity (continued) <TextView android:id="@+id/phoneNum" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="@dimen/phoneTextSize" /> </LinearLayout> File name is contact.xml. ©SoftMoore Consulting
Class SimpleCursorAdapter Class CursorAdapter maps columns from a cursor to TextViews or ImageViews defined in an XML file. Class SimpleCursorAdapter is a subclass of CursorAdapter that is simple and easy to use. To use a SimpleCursorAdapter, specify the columns from the cursor, the views to display the columns (e.g., TextViews), and the XML files that defines the appearance of these views (i.e., the default layout and the row layout). Note: The Cursor must include a column named "_id" or this class will not work. ©SoftMoore Consulting
Example: SimpleCursorAdapter // in MainActivity, which extends ListActivity private static final String[] COLUMNS = { "name", "phone_num" }; private static final int[] VIEWS = { R.id.name, R.id.phoneNum }; private SQLiteOpenHelper openHelper; ... @Override public void onCreate(Bundle savedInstanceState) { openHelper = new EmergencyDbOpenHelper(this); setContactListAdapter(); } (continued on next slide) ©SoftMoore Consulting
Example: SimpleCursorAdapter (continued) private void setContactListAdapter() { try String query = "select * from " + EmergencyContract.TABLE_NAME; SQLiteDatabase db = openHelper.getReadableDatabase(); Cursor cursor = db.rawQuery(query, null); SimpleCursorAdapter adapter = new SimpleCursorAdapter (this, R.layout.contact, cursor, COLUMNS, VIEWS, 0); setListAdapter(adapter); } catch (Exception ex) String errorMsg = "Error retrieving emergency contacts"; Log.e(LOG_TAG, errorMsg, ex); layout for rows these columns get mapped to these views ©SoftMoore Consulting
Comments on the Example Not every column returned by the cursor needs to be mapped to a view; e.g., _id is not mapped to a view in this example. Calling setContactListAdapter() after a change to the database will requery the database and reset the list in the display. ©SoftMoore Consulting
Displaying the ListView ©SoftMoore Consulting
Example: Handling Item Click Events @Override protected void onListItemClick(ListView listView, View view, int position, long id) { ViewGroup vg = (ViewGroup) view; TextView nameView = (TextView) vg.getChildAt(0); String name = nameView.getText().toString(); TextView phoneNumView = (TextView) vg.getChildAt(1); String phoneNum = phoneNumView.getText().toString(); callContact(phoneNum); } Alternatively, use ListView lv = getListView(); lv.setOnItemClickListener(…); ©SoftMoore Consulting
Example: Handling Item Click Events (continued) private void callContact(String phoneNum) { Uri uri = Uri.parse("tel:" + phoneNum); Intent intent = new Intent(Intent.ACTION_CALL, uri); startActivity(intent); } Important: When making a phone call, remember to add <uses-permission android:name="android.permission.CALL_PHONE" /> before the application element in AndroidManifest.xml. ©SoftMoore Consulting
Parameters to Method onListItemClick() parent: The ListView where the click happened. view: The view within the ListView that was clicked position: The position of the view in the adapter. 0-based position numbers id: The row id of the item that was clicked. from the database ©SoftMoore Consulting
Comments on Using an Unmanaged Cursor The approach described in this section for creating a ListView using a SimpleCursorAdapter and a SQLite database is acceptable if the database is relatively small and the data doesn’t change (no re-query necessary). For larger databases or when re-query is necessary: Older versions of Android used a method named startManagingCursor(), deprecated in API 11, that managed the cursor’s lifecycle based on the activity’s lifecycle. As described in subsequent sections, the more modern approach is to implement a content provider and to use classes CursorLoader and LoaderManager. ©SoftMoore Consulting
Relevant Links Data Storage Class SQLiteDatabase http://developer.android.com/guide/topics/data/data-storage.html Class SQLiteDatabase http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html Class SQLiteOpenHelper http://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper.html SQLite (home page) http://www.sqlite.org/ Android Database Example (slightly different approach) http://examples.javacodegeeks.com/android/core/database/android-database-example/ Appendix H - SQL Primer PowerPoint Slides for this course ©SoftMoore Consulting