In This Chapter
• Fetching Desired Rows from Data Tables
• Accessing Databases Through the ADB
• Accessing Databases Through Menus
• Creating Data Entry Forms
• Displaying Table Rows Via ListView
User-entered data needs to be saved somewhere so that it can be accessed at a later time. Android offers the SQLite relational database library for persisting data. It’s an open-source, lightweight, and powerful database available in the form of a C library. It uses SQL (Structured Query Language) for storing and retrieving information and performing database maintenance. SQLite is extremely reliable and is popularly used in devices with restricted computing power. Through SQLite, we can create individual databases for each application and store and manage application-related data.
Android databases are stored in the /data/data/<package_name>/databases
folder on devices or emulators.
To create an Android application that stores, accesses, and manipulates user information in a SQLite-relational database, we use the SQLiteOpenHelper
class. The SQLiteOpenHelper
class is an abstract class that provides a way to get read or write access to a database. The SQLiteOpenHelper
class thus makes our task of manipulating the data stored in the database easy.
The SQLiteOpenHelper
class is an abstract class used to create, open, and upgrade databases. It provides several methods including getWritableDatabase()
, getReadableDatabase()
, and close()
that returns an SQLiteDatabase
object that lets the calling code do the reading, writing, and closing of the database, respectively.
The SQLiteDatabase
class also provides methods including insert()
and query()
, which are used to insert rows in the database table and execute different SQLite queries on the database table. The query()
method accepts several parameters such as the database table to query, columns, and criteria, and returns a Cursor
representing the rows that satisfy the supplied criteria. The Cursor
class provides several methods to traverse the result set and move to the desired row and column position in the result set.
To understand how the information in an Android application can be stored and accessed from an SQLite database, let’s create a new Android application.
Launch Eclipse IDE and create a new Android project called DatabaseApp
. In this application, we create a database called shopping
, and in it we create a table called products
. The products
table consists of three columns: code
, product_name
, and price
to store the code, product name, and price, respectively. To the products
table, we add a couple of rows. Also, we access the rows in the products
table and display them via the TextView
control. To take advantage of the SQLiteOpenHelper
class and its method, we need to extend it. That is, we add a Java class to our application and make it inherit the SQLiteOpenHelper
class. The SQLiteOpenHelper
class makes the task of maintaining the SQLite database easier by hiding the complexities of maintaining the database.
To add a Java class to our application, right-click the com.androidunleashed.database-app
package in the Package Explorer
window and select New
, Class
option. We see a New Java Class
dialog box used to enter the new Java class information (see Figure 8.1). Enter the Java class name as DatabaseManager
. The two boxes, Source folder
and Package,
appear automatically filled with the src
folder location and the application package name, respectively. In the Modifiers
options, public
is selected by default. Keeping all the default information, click Finish
to create the new Java file DatabaseManager.java.
The newly added DatabaseManager.java
file has the initial contents shown here:
package com.androidunleashed.databaseapp;
public class DatabaseManager {
}
We write code in DatabaseManager.java
to perform the following tasks:
• Inherit from the SQLiteOpenHelper
class to access and take advantage of the methods defined in it.
• Open and return the writable and readable database file instance to perform writing and reading operations on the database, respectively.
• Create a database table, products
, consisting of three columns: code
, product_name
, and price
.
• Add rows to the products
table. The information for the new product is supplied by the activity file DatabaseAppActivity.java
.
• Fetch rows from the products
table and return them to the activity file DatabaseAppActivity.java
for displaying on the screen.
Note
The Java file is accessed and executed from the Java activity file of our application DatabaseAppActivity.java
.
To perform these tasks, the code shown in Listing 8.1 is written into the Java file DatabaseManager.java
.
package com.androidunleashed.databaseapp;
import android.database.sqlite.SQLiteDatabase;
import android.content.Context;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class DatabaseManager {
public static final String DB_NAME = "shopping";
public static final String DB_TABLE = "products";
public static final int DB_VERSION = 1;
private static final String CREATE_TABLE = "CREATE TABLE " + DB_TABLE + " (code
INTEGER PRIMARY KEY, product_name TEXT, price FLOAT);";
private SQLHelper helper;
private SQLiteDatabase db;
private Context context;
public DatabaseManager(Context c){
this.context = c;
helper=new SQLHelper(c);
this.db = helper.getWritableDatabase();
}
public DatabaseManager openReadable() throws android.database.SQLException {
helper=new SQLHelper(context);
db = helper.getReadableDatabase();
return this;
}
public void close(){
helper.close();
}
public void addRow(Integer c, String n, Float p){
ContentValues newProduct = new ContentValues();
newProduct.put("code", c);
newProduct.put("product_name", n);
newProduct.put("price", p);
try{db.insertOrThrow(DB_TABLE, null, newProduct);}
catch(Exception e)
{
Log.e("Error in inserting rows ", e.toString());
e.printStackTrace();
}
}
public String retrieveRows(){
String[] columns = new String[]{"code", "product_name", "price"};
Cursor cursor = db.query(DB_TABLE, columns, null, null, null, null, null);
String tablerows = "";
cursor.moveToFirst();
while (cursor.isAfterLast() == false) {
tablerows = tablerows + cursor.getInt(0) + ", "+cursor.getString(1)+", "
+ cursor.getFloat(2)+ "
";
cursor.moveToNext();
}
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
return tablerows;
}
public class SQLHelper extends SQLiteOpenHelper {
public SQLHelper(Context c){
super(c, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w("Products table","Upgrading database i.e. dropping table and rec-
reating it");
db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
onCreate(db);
}
}
}
Before we look at the code in detail, let’s examine what the code is doing.
The DatabaseManager
is a class where we write all the methods related to database maintenance such as creating databases and tables, upgrading tables, and adding and retrieving rows to and from the database table. To make our task easier, the DatabaseManager
class includes a SQLHelper
class, which inherits the SQLiteOpenHelper
class.
In this code, we see that an SQLHelper
class is defined in the DatabaseManager
class, which inherits the SQLiteOpenHelper
class. The SQLHelper
class instance, helper,
is created by passing the context
to its constructor. The SQLHelper
’s constructor in turn invokes the constructor of the SQLiteOpenHelper
class passing the context
database name (shopping)
and its version number (1)
to it. The helper
instance is set to refer to version 1
of the database, shopping
. The SQLiteOpenHelper
class provides two methods, getWritableDatabase()
and getReadableDatabase()
, that are used to open and return the writable and readable instance of the referred database. We can see that the getWritableDatabase()
method is called on by the helper
instance to get an SQLiteDatabase
object called db
. The SQLiteDatabase
object, db
, now refers to the shopping
database that is opened in write mode, and we can perform write operations on the database.
The SQLiteOpenHelper onCreate
and onUpgrade
class methods are overridden in the SQLHelper
class to create a new database and to upgrade it to a new version. The onCreate()
method creates a products
table in the shopping
database by calling the execSQL()SQLiteDatabase
method through its object, db
. The products
database table consists of three columns: code
, product_name
, and price
, where code
is the primary key in the products
table. In the onUpgrade
method, the existing table, products
, is dropped and re-created.
The openReadable()
method calls the getReadableDatabase()
on the helper
object to get the SQLiteDatabase
object db
that refers to the readable instance of the shopping
database. The close()
method calls the close()
method on the helper
class to close it.
The addRow()
method accepts the values for the new row of the products
table as parameters and inserts them in the products
table by calling the SQLiteDatabase insert-OrThrow()
method through its object db
. The ContentValues
is used to insert rows in the products table. Each ContentValues
object represents a single table row as a map of column names to values. A ContentValues
object, newProduct
, is created, and through its put()
methods, we supply the code
, product_name
, and price
columns’ values. Thereafter, the insertOrThrow()
method is called, and the ContentValues
object, newProduct
, is passed to it to insert a new row in the table, supplying the content specified in the ContentValues
object. An exception is thrown in case an error occurs while inserting the row.
The retrieveRows()
method fetches all the rows from the products
table by executing the query()
method of the SQLiteDatabase
object and returns in the form of a Cursor
object. The query()
method helps in running the query with the specified criteria to get the desired number of rows.
To fetch the desired rows from a database table, we execute queries containing a criterion against the given database. The queries are executed through the query()
method provided by the SQLiteDatabase
class. The parameters that are passed to the query()
method are shown here: db.query(bool_uniq, db_table, array_columns, where_clause, select_arg, group_by, having_clause, order);
.
Table 8.1 shows the meaning of these parameters.
Examples:
The following two queries return all the products
table rows for the code
, product_name
, and price
columns. The first query confirms that only unique rows should be fetched:
public static final String DB_TABLE = "products";
String[] columns = new String[]{"code", "product_name", "price"};
Cursor cursor = db.query(true, DB_TABLE, columns, null, null, null, null, null);
Cursor cursor = db.query(DB_TABLE, columns, null, null, null, null, null);
The following query returns the rows with the product name Camera
and the result set is sorted on the product code:
String whereClause="product_name=Camera";
String order="code";
Cursor cursor = db.query(DB_TABLE, columns, whereClause, null, null, null, order);
The returned rows from the query()
method are in the form of Cursor
objects. A Cursor
object does not create a separate result set of the extracted rows, but points at the result set that is part of a database table. When we call the moveToFirst()
method on the Cursor
object, the cursor is moved to the first row in the result set. Thereafter, all the products
table rows that exist in the Cursor
object are fetched one by one by calling the moveTo-Next()
method and returned. The Cursor
is closed when all the rows pointed at in its result set are traversed.
Cursors are pointers pointing at the result set of the underlying data and can be set to traverse the rows and retrieve column data of the result set. The Cursor
class provides several methods that can be used to set the pointer at the desired row and column position of the result set. Table 8.2 lists the methods provided by a Cursor
object to set the pointer at the desired location in the result set.
The DatabaseManager
class is accessed through an instance created in our Activity file. Let’s write the code for it. Modify the DatabaseAppActivity.java
file to appear as shown in Listing 8.2.
package com.androidunleashed.databaseapp;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class DatabaseAppActivity extends Activity {
private DatabaseManager mydManager;
private TextView response;
private TextView productRec;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_database_app);
response=(TextView)findViewById(R.id.response);
productRec=(TextView)findViewById(R.id.prodrec);
mydManager = new DatabaseManager(this);
mydManager.addRow(101, "Camera", Float.parseFloat("15"));
mydManager.addRow(102, "Laptop", Float.parseFloat("1005.99"));
mydManager.close();
mydManager = new DatabaseManager(this);
mydManager.openReadable();
String tableContent = mydManager.retrieveRows();
response.setText("The rows in the products table are:");
productRec.setText(tableContent);
mydManager.close();
}
}
We can see that TextView
controls with the response
and prodrec
IDs are captured from the layout file activity_database_app.xml
and mapped to the TextView
objects, response
and productRec
, respectively. To access different methods defined in the DatabaseManager
Java class that we just created above, its instance is created called mydManager
. Through the mydManager
instance, we access the addRow()
method of DatabaseManager
to add two rows to the products
table of our shopping
database. After we insert the rows in the products
table, mydManager
is closed so that it can be re-created to open the database in read mode. The readable database instance is obtained by calling the openReadable()
method of the DatabaseManager
class through its instance mydManager
. The text in the response TextView
control is set to The rows in the products table are:
. The text is a header for the products
rows that are displayed through another TextView
control. All the rows in the products
table are accessed by calling the retrieveRows()
method and displayed via the TextView
object productRec
.
It’s clear that we need to define two TextView
controls in our applications, one to display the rows in the products
table and the other as the header or title for the rows being displayed. To define two TextView
controls in activity_database_app.xml
, use the code shown in Listing 8.3.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/response" />
<TextView
android:id="@+id/prodrec"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Our application is complete, and we can run it. The rows in the products
database table are displayed, as shown in Figure 8.2.
To confirm whether the two rows we inserted with the previous application were actually inserted into the products
table of our shopping
SQLite database, let’s learn how to access the database through ADB.
Recall from Chapter 1, “Introduction to Android,” that the ADB (Android Debug Bridge) is a client/server program that is part of the Android SDK and is used to communicate with, control, and manage Android devices and emulators. We can perform several tasks via adb
commands, including viewing applications, deleting them, installing new applications, and executing shell commands.
To access the ADB through the Windows operating system, open the command prompt and navigate to the folder where adb.exe
is located. By default, the file, adb.exe
is installed in C:Program Files (x86)Androidandroid-sdkplatform-tools
. We can issue the following commands to interact with a device or emulator.
The adb devices
command shows the list of currently running emulator(s) and devices:
C:Program Files (x86)Androidandroid-sdkplatform-tools>adb devices
List of devices attached
emulator-5554 offline
The adb shell
command activates the shell, where we can issue Unix-like commands:
C:Program Files (x86)Androidandroid-sdkplatform-tools>adb shell
#
The ls -l
long listing command shows the list of directories in the emulator/device:
# ls -l
ls -l
drwxrwxrwt root root 2011-08-06 09:14 sqlite_stmt_journals
drwxrwx--- system cache 2011-07-22 23:14 cache
d---rwxrwx system system 1970-01-01 05:30 sdcard
lrwxrwxrwx root root 2011-08-06 09:14 etc -> /system/etc
drwxr-xr-x root root 2009-07-01 05:54 system
drwxr-xr-x root root 1970-01-01 05:30 sys
drwxr-x--- root root 1970-01-01 05:30 sbin
dr-xr-xr-x root root 1970-01-01 05:30 proc
-rwxr-x--- root root 9075 1970-01-01 05:30 init.rc
-rwxr-x--- root root 1677 1970-01-01 05:30 init.goldfish.rc
-rwxr-x--- root root 106568 1970-01-01 05:30 init
-rw-r--r-- root root 118 1970-01-01 05:30 default.prop
drwxrwx--x system system 2009-07-01 05:54 data
drwx------ root root 1970-01-01 05:30 root
drwxr-xr-x root root 2011-08-06 09:14 dev
The ls /data/data
command shows the list of files and directories in the subdirectory of the data directory. We see the list of all installed applications in the emulator. The installed application’s package names are also displayed:
# ls /data/data
ls /data/data
com.android.mms
com.android.googlesearch
com.android.launcher
:::::::::::
:::::::::::
com.androidunleashed.databaseapp
The following command shows the content of the application package’s databases directory:
# ls /data/data/com.androidunleashed.databaseapp/databases
ls /data/data/com.androidunleashed.databaseapp/databases
shopping
The output, shopping
, confirms that the shopping
database has been created in our application package com.androidunleashed.databaseapp
.
Let’s go into the databases
directory of our application package:
# cd /data/data/com.androidunleashed.databaseapp/databases
cd /data/data/com.androidunleashed.databaseapp/databases
On executing the ls
command, we see the list of files and directories in the databases
subdirectory. As expected, the name of our shopping database is displayed:
# ls
ls
shopping
Then, we make the shopping
database active by using the sqlite3 shopping
command:
# sqlite3 shopping
sqlite3 shopping
SQLite version 3.5.9
Enter ".help" for instructions
The SQLite
version information is displayed with the directive to type the .help
command to see help instructions. We also see an sqlite>
prompt to issue database-related commands.
The .tables
command displays the tables that exist in the currently active database:
sqlite> .tables
.tables
android_metadata products
The .schema
command displays the structure (field names, their types, width, and so on) of all the tables that exist in the currently active database:
sqlite> .schema
.schema
CREATE TABLE android_metadata (locale TEXT);
CREATE TABLE products (code INTEGER PRIMARY KEY, product_name TEXT, price
FLOAT)
;
The SQL SELECT
command can be used to see the number of rows in the products
table of our shopping
database:
sqlite> select * from products;
select * from products;
101|Camera|15.0
102|Laptop|1005.98999023438
This output confirms that our DatabaseApp
application successfully inserted two rows into the products
table.
We can also issue an SQL DELETE
command to delete a row. The following command deletes a row from the products
table that has a product code
equal to 102
.
sqlite> delete from products where code=102;
delete from products where code=102;
To confirm that the row was really deleted, we can give a SQL SELECT
command as shown here:
sqlite> select * from products;
select * from products;
101|Camera|15.0
This output shows only one row in the products
table, confirming that one of the two rows was deleted. Similarly we can give an SQL UPDATE
command to update or modify the information of any row. The following SQL UPDATE
command updates the product name to Handy Cam
, whose code is equal to 101
.
sqlite> update products set product_name='Handy Cam' where code=101;
update products set product_name='Handy Cam' where code=101;
We can confirm whether the row was successfully updated by issuing an SQL SELECT
command:
sqlite> select * from products;
select * from products;
101|Handy Cam|15.0
The preceding output confirms that the product name was updated. Similarly, we can give a different SQL command at the sqlite>
prompt to manipulate our database. When finished, we can leave the sqlite>
prompt by giving an .exit
command:
sqlite>.exit
Note
An alternative to viewing all this data is to transfer the database file to the developer machine and then use many of the free SQLite utilities to view the data in a simple GUI-like fashion. Remember, the preceding commands work only on emulators or rooted devices.
To make our DatabaseApp
application accessible through menus, we need to define a few menu items in the menu file activity_database_app
available in the res/menu
folder.
In the menu file, we define a menu consisting of two menu items called Insert Rows
and List Rows.
These are used to initiate the tasks of inserting and accessing rows from the products
table. The activity_database_app.xml
file with the two menu items appears as shown in Listing 8.4.
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/insert_rows"
android:title="Insert Rows"
android:icon="@drawable/ic_launcher" />
<item android:id="@+id/list_rows"
android:title="List Rows"
android:icon="@drawable/ic_launcher" />
</menu>
We can see that the IDs assigned to the two menu items of the menu are insert_rows
and list_rows
, respectively. In addition, the ic_launcher
image from the res/drawable
folder is displayed in the menu items. To insert and fetch rows from the database table when a menu item is selected, we need to write some Java code into the DatabaseAppActivity.java
activity file. Note, there is no need to change the Java file DatabaseManager.java
.
The Java code we adds performs the following tasks:
• Display the menu on the screen. Recall from the previous chapter that the onCreateOptionsMenu()
method is called when a user clicks the Menu
button on the device or emulator. So, in the activity file, we need to override this method to inflate the menu defined in the mymenu.xml
file.
• Enclose the commands for inserting and fetching rows from the database table into two separate methods so that they can be invoked when the respective menu item is selected.
• Handle menu item selections and direct them to the respective methods for performing desired tasks.
To do all these tasks, the code shown in Listing 8.5 is written into the Java activity file DatabaseAppActivity.java
. Only the code in bold is added or modified code; the rest of the code is the same as we saw in Listing 8.2.
package com.androidunleashed.databaseapp;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
public class DatabaseAppActivity extends Activity {
private DatabaseManager mydManager;
private TextView response;
private TextView productRec;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_database_app);
response=(TextView)findViewById(R.id.response);
productRec=(TextView)findViewById(R.id.prodrec);
response.setText("Press MENU button to display menu");
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.activity_database_app, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.insert_rows: insertRec();
break;
case R.id.list_rows: showRec();
break;
}
return true;
}
public boolean insertRec(){
mydManager = new DatabaseManager(this);
mydManager.addRow(101, "Camera", Float.parseFloat("15"));
mydManager.addRow(102, "Laptop", Float.parseFloat("1005.99"));
response.setText("The rows in the products table are inserted");
productRec.setText("");
mydManager.close();
return true;
}
public boolean showRec(){
mydManager = new DatabaseManager(this);
mydManager.openReadable();
String tableContent = mydManager.retrieveRows();
response.setText("The rows in the products table are:");
productRec.setText(tableContent);
mydManager.close();
return true;
}
}
We can see that the response TextView
object is set to display the text Press MENU button to display menu
. The Activity class’s onCreateOptionsMenu()
method is overridden to display the menu defined in the mymenu.xml
file.
Initially, there is no menu defined in the menu
parameter of the onCreateOptionsMenu()
method. To inflate the menu that we defined in mymenu.xml
, we get the MenuInflater
from the Activity class and invoke its inflate
method to inflate, or merge, our own menu defined in mymenu.xml
to the menu
parameter of this method. The onCreateOptionsMenu()
method is set to return the boolean value, true
, to allow Android to display the menu. Two methods, insertRec()
and showRec()
, are defined that contain the code to insert and fetch rows from the database table. In the insertRec()
method, a DatabaseManager
class instance is created called mydManager
, and through this instance, the addRow()
method is invoked to insert two rows in the products
table. On successful insertion of the rows, the TextView
response displays the text: The rows in the products table are inserted
.
In the showRec()
method, the DatabaseManager
class instance called mydManager
is created. It obtains the read-only reference of the database and invokes the DatabaseManager
class’s retrieveRows()
method that fetches all the rows in the products
table and returns a string. The rows returned as a string by retrieveRows()
are assigned to the product-Rec TextView
for display. To display the header above the displayed rows, the response TextView
object is set to The rows in the products table are:
.
The onOptionItemSelected()
method handles the menu item selections. It calls the insertRec()
and showRec()
methods when the menu items with the ID insert_rows
and list_rows
are selected by the user.
After running the application, we get the initial screen shown in Figure 8.3 (left), where the TextView
control prompts to call the menu. The menu appears with two menu items, Insert Rows
and List Rows
. After we select Insert Rows
, the activity class method insert-Rec()
is executed, which inserts two rows in the products
table and displays the message The rows in the products table are inserted
(see Figure 8.3—middle). After we select List Rows
, the rows in the products
database table are accessed and displayed on the screen through the TextView
control, as shown in Figure 8.3 (right).
Our application looks pretty good, but there seems to be a limitation. It doesn’t ask the user to provide information for adding new products to the products
table. Instead the two rows to be added are hard-coded. To add more user interaction, we need to allow the user to enter new product information to the products
table. Let’s see how it can be done.
To create a form for entering new product information (see Figure 8.5—left), three TextViews
, three EditText
controls, and two Button
controls are arranged in tabular format in the TableLayout
. The layout file activity_database_app.xml
is modified to appear as shown in Listing 8.6. Only the code in bold is the newly added code; the rest is the same as we saw in Listing 8.3.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/response" />
<TextView
android:id="@+id/prodrec"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TableLayout
android:id="@+id/add_table"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TableRow >
<TextView
android:text="Product Code:"
android:padding="3dip" />
<EditText
android:id="@+id/prod_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</TableRow>
<TableRow >
<TextView
android:text="Product Name:"
android:padding="3dip" />
<EditText
android:id="@+id/prod_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="150dip" />
</TableRow>
<TableRow >
<TextView
android:text="Product Price:"
android:padding="3dip" />
<EditText
android:id="@+id/prod_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="50dip" />
</TableRow>
<TableRow >
<Button
android:id="@+id/add_button"
android:text="Add Product"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="3dip" />
<Button
android:id="@+id/cancel_button"
android:text="Cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="3dip" />
</TableRow>
</TableLayout>
</LinearLayout>
We can see that the three TextView
controls are set to display the text Product Code:
, Product Name:
, and Product Price:
. The captions in the two Button
controls are set to Add Product
and Cancel
. The IDs assigned to the three EditText
controls are prod_code
, prod_name
, and prod_price
. The Button
controls are assigned the IDs add_button
and cancel_button
.
There are no major changes to the Java file DatabaseManager.java
. Only three statements are modified, as shown in bold in Listing 8.7.
package com.androidunleashed.databaseapp;
import android.database.sqlite.SQLiteDatabase;
import android.content.Context;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class DatabaseManager {
public static final String DB_NAME = "shopping";
public static final String DB_TABLE = "products";
public static final int DB_VERSION = 1;
private static final String CREATE_TABLE = "CREATE TABLE " + DB_TABLE + " (code
INTEGER PRIMARY KEY, product_name TEXT, price FLOAT);";
private SQLHelper helper;
private SQLiteDatabase db;
private Context context;
public DatabaseManager(Context c){
this.context = c;
helper=new SQLHelper(c);
this.db = helper.getWritableDatabase();
}
public DatabaseManager openReadable() throws android.database.SQLException {
helper=new SQLHelper(context);
db = helper.getReadableDatabase();
return this;
}
public void close(){
helper.close();
}
public boolean addRow(int c, String n, float p){
ContentValues newProduct = new ContentValues();
newProduct.put("code", c);
newProduct.put("product_name", n);
newProduct.put("price", p);
try{db.insertOrThrow(DB_TABLE, null, newProduct);}
catch(Exception e) {
Log.e("Error in inserting rows ", e.toString());
e.printStackTrace();
return false;
}
db.close();
return true;
}
public String retrieveRows(){
String[] columns = new String[]{"code", "product_name", "price"};
Cursor cursor = db.query(DB_TABLE, columns, null, null, null, null, null);
String tablerows = "";
cursor.moveToFirst();
while (cursor.isAfterLast() == false) {
tablerows = tablerows + cursor.getInt(0) + ", "+cursor.getString(1)+",
"+ cursor.getFloat(2)+ "
";
cursor.moveToNext();
}
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
return tablerows;
}
public class SQLHelper extends SQLiteOpenHelper {
public SQLHelper(Context c){
super(c, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w("Products table","Upgrading database i.e. dropping table and rec-
reating it");
db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
onCreate(db);
}
}
}
We can see that the addRow()
method is set to return a boolean value instead of void
, as it was before. The reason is that we use the returned boolean value to see whether the row was successfully inserted into the products
table. The database is closed after the row insertion in the products
table.
In the activity file, we need to write Java code to perform the following tasks:
• Access the TableLayout
container and make it visible when the Insert Rows
menu item is selected. Also make it invisible when the List Rows
menu item is selected.
• Map the EditText
and Button
controls defined in the TableLayout
to the EditText
and Button
objects, respectively.
• Associate ClickListener
to the Add Product
button defined in the TableLayout
to listen for the click event occurrence on the Button
control.
• Through the callback event of the ClickListener
, onClick()
, access the new product information entered by the user and send it to the addRow()
method defined in DatabaseManager.java
as parameters to insert them in the products
table.
• Hide the soft keyboard when the user is finished entering new product information.
To perform these tasks, add the code shown in Listing 8.8 into the DatabaseAppActivity.java
activity file. Only the code in bold is the newly added code; the rest is the same as we saw in Listing 8.5.
package com.androidunleashed.databaseapp;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.widget.TextView;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TableLayout;
import android.view.View.OnClickListener;
import android.view.inputmethod.InputMethodManager;
public class DatabaseAppActivity extends Activity {
private DatabaseManager mydManager;
private TextView response;
private TextView productRec;
EditText pcode, pname, price;
Button addButton;
private TableLayout addLayout;
private boolean recInserted;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_database_app);
response=(TextView)findViewById(R.id.response);
productRec=(TextView)findViewById(R.id.prodrec);
addLayout=(TableLayout)findViewById(R.id.add_table);
addLayout.setVisibility(View.GONE);
response.setText("Press MENU button to display menu");
Button addButton = (Button) findViewById(R.id.add_button);
addButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mydManager = new DatabaseManager(DatabaseAppActivity.this);
pcode=(EditText)findViewById(R.id.prod_code);
pname=(EditText)findViewById(R.id.prod_name);
price=(EditText)findViewById(R.id.prod_price);
recInserted=mydManager.addRow(Integer.parseInt(pcode.getText().
toString()), pname.getText().toString(),
Float.parseFloat (price.getText().toString()));
addLayout.setVisibility(View.GONE);
if(recInserted)
response.setText("The row in the products table is inserted");
else
response.setText("Sorry, some errors occurred while inserting
the row in the products table");
InputMethodManager imm = (InputMethodManager)
getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(price.getWindowToken(), InputMethodMa-
nager.HIDE_NOT_ALWAYS);
mydManager.close();
pcode.setText("");
pname.setText("");
price.setText("");
productRec.setText("");
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.activity_database_app, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.insert_rows: addLayout.setVisibility(View.VISIBLE);
response.setText("Enter information of the new product");
productRec.setText("");
break;
case R.id.list_rows: showRec();
break;
}
return true;
}
public boolean showRec(){
addLayout.setVisibility(View.GONE);
mydManager = new DatabaseManager(this);
mydManager.openReadable();
String tableContent = mydManager.retrieveRows();
response.setText("The rows in the products table are:");
productRec.setText(tableContent);
mydManager.close();
return true;
}
}
We can see that the TableLayout
container is captured from the layout file and mapped to the TableLayout
object addLayout
. It is initially set to the GONE
mode and is be displayed only when the InsertRows
menu item is selected by the user. The TableLayout
contains controls to enter new product information. The Button
control defined in the TableLayout
with the ID add_button
, is accessed and mapped to the Button
object add_button
. A ClickListener
is associated with the add_button
object so that the callback method, onClick()
, is executed when the Button
with the caption Add Button
is selected by the user.
In the onClick()
method, the information entered by the user in the three EditText
controls, prod_code
, prod_name
, and prod_price
, is accessed, converted into a string data type, and passed as arguments to the addRow()
method defined in DatabaseManager.java
. To add a new row in the products
table, the addRow()
method accepts three parameters of the string data type. The boolean value returned by addRow()
is checked to see whether the new row was successfully inserted in the products
table. If the boolean value returned by addRow()
is true
, it means that the new row was successfully inserted and the message The row in the products table is inserted
is displayed. If not, the message Sorry, some errors occurred while inserting the row in the products table
is shown instead. Finally, the soft keyboard that appeared while inserting new product information is hidden.
Note, there is no need to make any changes to the menu file mymenu.xml
.
Our application is complete and is ready to accept new product information from the user. After we run the application, the first screen that we see on startup is shown in Figure 8.4 (left). The screen displays a message via a TextView
control asking the user to select the MENU
button. Two menu items, Insert Rows
and List Rows
, are displayed. After we select the List Rows
menu item, the information stored in the products
table is displayed, as shown in Figure 8.4 (right).
After we select the Insert Rows
menu item, a form is displayed for entering new product information. A TextView
at the top displays the text Enter information of the new product
. The user can enter the new product information, as shown in the Figure 8.5 (left), and then click the Add Product
button to add a new row into the products
table. If the row is successfully inserted, the message The row in the products table is inserted
appears on the screen, as shown in Figure 8.5 (middle). After we select the List Rows
menu item, the newly added row is displayed on the screen, as shown in Figure 8.5 (right).
If, for any reason, the row could not be added to the products table, the error message Sorry, some errors occurred while inserting the row in the products table
appears on the screen.
We can make our application more attractive and interactive by displaying the products
table rows via the ListView
control, shown in Figure 8.6, instead of through the TextView
control shown in Figure 8.5 (right).
To display the information fetched from the database table with the ListView
control instead of the TextView
control, the first step is to replace the response TextView
that we used to display products
table rows with a ListView
control. After we replace the TextView
control with a ListView
control, activity_database_app.xml
appears as shown in Listing 8.9. Only the code in bold is modified; the rest of the code is the same as we saw in Listing 8.6.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/response" />
<ListView
android:id="@+id/prodrec"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:drawSelectorOnTop="false"/>
<TableLayout
android:id="@+id/add_table"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TableRow >
<TextView
android:text="Product Code:"
android:padding="3dip" />
<EditText
android:id="@+id/prod_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</TableRow>
<TableRow >
<TextView
android:text="Product Name:"
android:padding="3dip" />
<EditText
android:id="@+id/prod_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="150dip" />
</TableRow>
<TableRow >
<TextView
android:text="Product Price:"
android:padding="3dip" />
<EditText
android:id="@+id/prod_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="50dip"/>
</TableRow>
<TableRow >
<Button
android:id="@+id/add_button"
android:text="Add Product"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="3dip"/>
<Button
android:id="@+id/cancel_button"
android:text="Cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="3dip" />
</TableRow>
</TableLayout>
</LinearLayout>
In the Java file, DatabaseManager.java
, we need to modify the retrieveRows()
method that returns the products
table rows in string data format. Currently, the retrieveRows()
method is set to return a data of type String
, as the information returned by it was displayed via a TextView
control. Now, because we want to display the data via a ListView
control, we must modify the method return type to ArrayList<String>
. A productRows
of ArrayList<String>
variable data type is defined, and every row fetched from the products
table is added to it. The method returns the variable productRows
, containing the products
table rows in the ArrayList
format. The Java file DatabaseManager.java
is modified to appear as shown in Listing 8.10. Only the statements shown in bold are modified; the rest of the code is the same as we saw in Listing 8.7.
package com.androidunleashed.databaseapp;
import android.database.sqlite.SQLiteDatabase;
import android.content.Context;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import java.util.ArrayList;
public class DatabaseManager {
public static final String DB_NAME = "shopping";
public static final String DB_TABLE = "products";
public static final int DB_VERSION = 1;
private static final String CREATE_TABLE = "CREATE TABLE " + DB_TABLE + " (code
INTEGER PRIMARY KEY, product_name TEXT, price FLOAT);";
private SQLHelper helper;
private SQLiteDatabase db;
private Context context;
public DatabaseManager(Context c){
this.context = c;
helper=new SQLHelper(c);
this.db = helper.getWritableDatabase();
}
public DatabaseManager openReadable() throws android.database.SQLException {
helper=new SQLHelper(context);
db = helper.getReadableDatabase();
return this;
}
public void close(){
helper.close();
}
public boolean addRow(int c, String n, float p){
ContentValues newProduct = new ContentValues();
newProduct.put("code", c);
newProduct.put("product_name", n);
newProduct.put("price", p);
try{db.insertOrThrow(DB_TABLE, null, newProduct);}
catch(Exception e)
{
Log.e("Error in inserting rows ", e.toString());
e.printStackTrace();
return false;
}
db.close();
return true;
}
public ArrayList<String> retrieveRows(){
ArrayList<String> productRows=new ArrayList<String>();
String[] columns = new String[]{"code", "product_name", "price"};
Cursor cursor = db.query(DB_TABLE, columns, null, null, null, null, null);
cursor.moveToFirst();
while (cursor.isAfterLast() == false) {
productRows.add(Integer.toString(cursor.getInt(0)) + ", "+cursor.get-
String(1)+",
"+Float.toString(cursor.getFloat(2)));
cursor.moveToNext();
}
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
return productRows;
}
public class SQLHelper extends SQLiteOpenHelper {
public SQLHelper(Context c){
super(c, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w("Products table","Upgrading database i.e. dropping table and rec-
reating it");
db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
onCreate(db);
}
}
}
Again, a small modification is required in DatabaseAppActivity.java
. In its showRec()
method, we write the code to access the ListView
control from the layout file and map it to the ListView
object, productRec
. The rows returned by calling the retrieveRows()
method of DatabaseManager
in the ArrayList<String>
format are temporarily stored in the variable tableContent.
An ArrayAdapter
called arrayAdpt
is created through the tableContent
array. Finally, the ArrayAdapter
, arrayAdpt
, is set to the ListView
control productRec
to display the products
table rows in the ListView
control. The activity file DatabaseAppActivity.java
is modified to appear as shown in Listing 8.11. Only the code in bold is modified; the rest of the code is exactly the same as we saw in Listing 8.8.
package com.androidunleashed.databaseapp;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.widget.TextView;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TableLayout;
import android.view.View.OnClickListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.ListView;
import android.widget.ArrayAdapter;
import java.util.ArrayList;
public class DatabaseAppActivity extends Activity {
private DatabaseManager mydManager;
private TextView response;
private ListView productRec;
EditText pcode, pname, price;
Button addButton;
private TableLayout addLayout;
private Boolean recInserted;
ArrayList<String> tableContent;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_database_app);
response=(TextView)findViewById(R.id.response);
productRec=(ListView)findViewById(R.id.prodrec);
addLayout=(TableLayout)findViewById(R.id.add_table);
addLayout.setVisibility(View.GONE);
response.setText("Press MENU button to display menu");
Button add_button = (Button) findViewById(R.id.add_button);
add_button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mydManager = new DatabaseManager(DatabaseAppActivity.this);
pcode=(EditText)findViewById(R.id.prod_code);
pname=(EditText)findViewById(R.id.prod_name);
price=(EditText)findViewById(R.id.prod_price);
recInserted=mydManager.addRow(Integer.parseInt (pcode.get-
Text().toString()), pname.getText().toString(),Float.parseFloat(price.getText().
toString()));
addLayout.setVisibility(View.GONE);
if(recInserted)
response.setText("The row in the products table is inserted");
else
response.setText("Sorry, some errors occurred while inserting
the row in the products table");
InputMethodManager imm = (InputMethodManager)
getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(price.getWindowToken(), InputMethodMa-
nager.HIDE_NOT_ALWAYS);
mydManager.close();
pcode.setText("");
pname.setText("");
price.setText("");
productRec.setVisibility(View.GONE);
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.activity_database_app, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.insert_rows:
addLayout.setVisibility(View.VISIBLE);
response.setText("Enter information of the new product");
productRec.setVisibility(View.GONE);
break;
case R.id.list_rows: showRec();
break;
}
return true;
}
public boolean showRec(){
addLayout.setVisibility(View.GONE);
mydManager = new DatabaseManager(this);
mydManager.openReadable();
tableContent = mydManager.retrieveRows();
response.setText("The rows in the products table are:");
productRec = (ListView)findViewById(R.id.prodrec);
ArrayAdapter<String> arrayAdpt=new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, tableContent);
productRec.setAdapter(arrayAdpt);
productRec.setVisibility(View.VISIBLE);
mydManager.close();
return true;
}
}
Our application is updated to show the database table rows via a ListView
control. After running the application, we get the usual startup screen (as you saw earlier in Figure 8.4—left), asking the user to bring up the menu. The menu shows two menu items, Insert Rows
and List Rows
, as shown earlier in Figure 8.4 (right). After we select the List Rows
menu item, the rows in the products
table are fetched and displayed via the ListView
control, as shown in Figure 8.6.
In this chapter we learned to use databases in Android applications. We saw how to use SQLiteOpenHelper
and cursors, as well as how to fetch desired rows from the table. We learned to access a database through the ADB and menus. Finally, we learned to create data entry forms and display database table rows via ListView
.
The next chapter focuses on understanding animation. We learn to use Canvas and Paints, measure screen coordinates, and apply frame-by-frame animation. We also learn about tweening animation and the use of interpolators.
3.16.165.255