If you have an android application that needs to sync data with a backend server but doesn't need the internet to be available all the time for it to work then you must know at all times whether your app has access to internet or not. As soon as you have internet connection you can get latest data from the server or post any changes saved in your app using HTTP service. In this example we extend the BroadcastReceiver to listen for any changes in network status and then find out whether the app has internet at that time. Please note that you have to specify the ACCESS_NETWORK_STATE in your android manifest permissions for this to work otherwise you will get java.lang.SecurityException: ConnectivityService: Neither user XXXXX nor current process has android.permission.ACCESS_NETWORK_STATE.
@Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.activity_main, menu); return true; }
public class NetworkChangeReceiver extends BroadcastReceiver {
@Override public void onReceive(final Context context, final Intent intent) {
Log.v(LOG_TAG, "Receieved notification about network status"); isNetworkAvailable(context);
}
private boolean isNetworkAvailable(Context context) { ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); if (connectivity != null) { NetworkInfo[] info = connectivity.getAllNetworkInfo(); if (info != null) { for (int i = 0; i < info.length; i++) { if (info[i].getState() == NetworkInfo.State.CONNECTED) { if(!isConnected){ Log.v(LOG_TAG, "Now you are connected to Internet!"); networkStatus.setText("Now you are connected to Internet!"); isConnected = true; //do your processing here --- //if you need to post any data to the server or get status //update from the server } return true; } } } } Log.v(LOG_TAG, "You are not connected to Internet!"); networkStatus.setText("You are not connected to Internet!"); isConnected = false; return false; } }
Why not make AJAX request using plain JavaScript. Well guess what it's not programmer friendly, that's where all these JavaScript frameworks such as jQuery, ExtJs, Dojo etc. are good at. They encapsulate a lot of the underlying technology and provide us programmers with clean API's that has lot of config options and event triggers to handle anything from a simple form submit to something more complicated.
The method for AJAX request is jQuery.ajax( url [, settings] )
The $.ajax() method returns the jqXHR object. URL is a string containing the URL to which the request is sent and settings are a set of key/value pairs that configure the Ajax request. All settings are optional.
In this tutorial we are going to cover the following topics ...
Capture form data and send that to the Server using AJAX request
Intercept the request before it was sent and add some extra parameters
Check if our request to server was successful or not
Display the JSON response from the Server
Setup connection to MySQL from Tomcat Server
Create Java Servlets to process our AJAX request and access MySQL database
We have created a form that takes a country code and then makes an AJAX request to get more information about the country if the country code is valid. Also we intercept the request using beforeSend config to add some dummy data to the request and disable the SUBMIT button until we receive a response from the server. After we receive the response we display the results inside the ajaxResponse DIV tag.
Application HTML file - index.html
jQuery Ajax POST data Request and Response Example
Application JavaScript file using jQuery - apps.js
$(document).ready(function() {
//Stops the submit request $("#myAjaxRequestForm").submit(function(e){ e.preventDefault(); });
//checks for the button click event $("#myButton").click(function(e){
//get the form data and then serialize that dataString = $("#myAjaxRequestForm").serialize();
//get the form data using another method var countryCode = $("input#countryCode").val(); dataString = "countryCode=" + countryCode;
//make the AJAX request, dataType is set to json //meaning we are expecting JSON data in response from the server $.ajax({ type: "POST", url: "CountryInformation", data: dataString, dataType: "json",
//if received a response from the server success: function( data, textStatus, jqXHR) { //our country code was correct so we have some information to display if(data.success){ $("#ajaxResponse").html(""); $("#ajaxResponse").append("Country Code: " + data.countryInfo.code + " "); $("#ajaxResponse").append("Country Name: " + data.countryInfo.name + " "); $("#ajaxResponse").append("Continent: " + data.countryInfo.continent + " "); $("#ajaxResponse").append("Region: " + data.countryInfo.region + " "); $("#ajaxResponse").append("Life Expectancy: " + data.countryInfo.lifeExpectancy + " "); $("#ajaxResponse").append("GNP: " + data.countryInfo.gnp + " "); } //display error message else { $("#ajaxResponse").html("
Country code in Invalid!
"); } },
//If there was no resonse from the server error: function(jqXHR, textStatus, errorThrown){ console.log("Something really bad happened " + textStatus); $("#ajaxResponse").html(jqXHR.responseText); },
//capture the request before it was sent to server beforeSend: function(jqXHR, settings){ //adding some Dummy data to the request settings.data += "&dummyData=whatever"; //disable the button until we get the response $('#myButton').attr("disabled", true); },
//this is called after the response or error functions are finsihed //so that we can take some action complete: function(jqXHR, textStatus){ //enable the button $('#myButton').attr("disabled", false); }
Android applications can access data directly from a SQLite database using the database helper but for other applications to get access to the same data you have to create a Content Provider. Content Providers encapsulate the data access, provide security using the Android Manifest and standardize the access via a Content URI. If the data is used only by your application there is no need to create a content provider but you can always do that in case a need that arises in the future.
The contentProvider must implement the following methods query(), insert(), update(), delete(), getType() and onCreate() to provide access to the underlying data. If you don't implement the delete method just send an exception to let the application know or if you don't get an URI match. If you have done SQLite database access using the database helper you will find there is not much difference here. All you have to do it create a provider that matches URIs and then figures out what to do with the request.
In this example we will create a simple maintenance program to maintain a list of countries. The access to the country database will be thru the Content Provider that will implement all the CRUD functions. On the screen we will display an existing list of countries and let the user add, update and delete a country. Here is the step by step process to implement the above mentioned functionality using the Content Provider.
Step 1: Create the SQLite database Object - CountriesDb.java
public static final String KEY_ROWID = "_id"; public static final String KEY_CODE = "code"; public static final String KEY_NAME = "name"; public static final String KEY_CONTINENT = "continent";
private static final String LOG_TAG = "CountriesDb"; public static final String SQLITE_TABLE = "Country";
public static void onCreate(SQLiteDatabase db) { Log.w(LOG_TAG, DATABASE_CREATE); db.execSQL(DATABASE_CREATE); }
public static void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w(LOG_TAG, "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data"); db.execSQL("DROP TABLE IF EXISTS " + SQLITE_TABLE); onCreate(db); }
}
Step 2: Create the SQLite database Helper - MyDatabaseHelper.java
public class MyContentProvider extends ContentProvider{
private MyDatabaseHelper dbHelper;
private static final int ALL_COUNTRIES = 1; private static final int SINGLE_COUNTRY = 2;
// authority is the symbolic name of your provider // To avoid conflicts with other providers, you should use // Internet domain ownership (in reverse) as the basis of your provider authority. private static final String AUTHORITY = "com.as400samplecode.contentprovider";
// create content URIs from the authority by appending path to database table public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/countries");
// a content URI pattern matches content URIs using wildcard characters: // *: Matches a string of any valid characters of any length. // #: Matches a string of numeric characters of any length. private static final UriMatcher uriMatcher; static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(AUTHORITY, "countries", ALL_COUNTRIES); uriMatcher.addURI(AUTHORITY, "countries/#", SINGLE_COUNTRY); }
// system calls onCreate() when it starts up the provider. @Override public boolean onCreate() { // get access to the database helper dbHelper = new MyDatabaseHelper(getContext()); return false; }
//Return the MIME type corresponding to a content URI @Override public String getType(Uri uri) {
switch (uriMatcher.match(uri)) { case ALL_COUNTRIES: return "vnd.android.cursor.dir/vnd.com.as400samplecode.contentprovider.countries"; case SINGLE_COUNTRY: return "vnd.android.cursor.item/vnd.com.as400samplecode.contentprovider.countries"; default: throw new IllegalArgumentException("Unsupported URI: " + uri); } }
// The insert() method adds a new row to the appropriate table, using the values // in the ContentValues argument. If a column name is not in the ContentValues argument, // you may want to provide a default value for it either in your provider code or in // your database schema. @Override public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = dbHelper.getWritableDatabase(); switch (uriMatcher.match(uri)) { case ALL_COUNTRIES: //do nothing break; default: throw new IllegalArgumentException("Unsupported URI: " + uri); } long id = db.insert(CountriesDb.SQLITE_TABLE, null, values); getContext().getContentResolver().notifyChange(uri, null); return Uri.parse(CONTENT_URI + "/" + id); }
// The query() method must return a Cursor object, or if it fails, // throw an Exception. If you are using an SQLite database as your data storage, // you can simply return the Cursor returned by one of the query() methods of the // SQLiteDatabase class. If the query does not match any rows, you should return a // Cursor instance whose getCount() method returns 0. You should return null only // if an internal error occurred during the query process. @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getWritableDatabase(); SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); queryBuilder.setTables(CountriesDb.SQLITE_TABLE);
switch (uriMatcher.match(uri)) { case ALL_COUNTRIES: //do nothing break; case SINGLE_COUNTRY: String id = uri.getPathSegments().get(1); queryBuilder.appendWhere(CountriesDb.KEY_ROWID + "=" + id); break; default: throw new IllegalArgumentException("Unsupported URI: " + uri); }
// The delete() method deletes rows based on the seletion or if an id is // provided then it deleted a single row. The methods returns the numbers // of records delete from the database. If you choose not to delete the data // physically then just update a flag here. @Override public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase(); switch (uriMatcher.match(uri)) { case ALL_COUNTRIES: //do nothing break; case SINGLE_COUNTRY: String id = uri.getPathSegments().get(1); selection = CountriesDb.KEY_ROWID + "=" + id + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); break; default: throw new IllegalArgumentException("Unsupported URI: " + uri); } int deleteCount = db.delete(CountriesDb.SQLITE_TABLE, selection, selectionArgs); getContext().getContentResolver().notifyChange(uri, null); return deleteCount; }
// The update method() is same as delete() which updates multiple rows // based on the selection or a single row if the row id is provided. The // update method returns the number of updated rows. @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { SQLiteDatabase db = dbHelper.getWritableDatabase(); switch (uriMatcher.match(uri)) { case ALL_COUNTRIES: //do nothing break; case SINGLE_COUNTRY: String id = uri.getPathSegments().get(1); selection = CountriesDb.KEY_ROWID + "=" + id + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); break; default: throw new IllegalArgumentException("Unsupported URI: " + uri); } int updateCount = db.update(CountriesDb.SQLITE_TABLE, values, selection, selectionArgs); getContext().getContentResolver().notifyChange(uri, null); return updateCount; }
}
Step 4: Row Layout for Country Display - country_info.xml
Step 5: Main Layout with ListView - activity_main.xml
Step 6: Main Activity using LoaderManager for Country List - MainActivity.java
The list of countries is displayed in a ListView using the CursorLoader that queries the ContentResolver and returns a Cursor. This class implements the Loader protocol in a standard way for querying cursors, building on AsyncTaskLoader to perform the cursor query on a background thread so that it does not block the application's UI. After the loader has finished its loading just swap the new cursor from the Content Provider and return the old Cursor.
public void onClick(View v) { // starts a new Intent to add a Country Intent countryEdit = new Intent(getBaseContext(), CountryEdit.class); Bundle bundle = new Bundle(); bundle.putString("mode", "add"); countryEdit.putExtras(bundle); startActivity(countryEdit); } });
}
@Override protected void onResume() { super.onResume(); //Starts a new or restarts an existing Loader in this manager getLoaderManager().restartLoader(0, null, this); }
private void displayListView() {
// The desired columns to be bound String[] columns = new String[] { CountriesDb.KEY_CODE, CountriesDb.KEY_NAME, CountriesDb.KEY_CONTINENT };
// the XML defined views which the data will be bound to int[] to = new int[] { R.id.code, R.id.name, R.id.continent, };
// create an adapter from the SimpleCursorAdapter dataAdapter = new SimpleCursorAdapter( this, R.layout.country_info, null, columns, to, 0);
// get reference to the ListView ListView listView = (ListView) findViewById(R.id.countryList); // Assign adapter to ListView listView.setAdapter(dataAdapter); //Ensures a loader is initialized and active. getLoaderManager().initLoader(0, null, this);
listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView listView, View view, int position, long id) { // Get the cursor, positioned to the corresponding row in the result set Cursor cursor = (Cursor) listView.getItemAtPosition(position);
// display the selected country String countryCode = cursor.getString(cursor.getColumnIndexOrThrow(CountriesDb.KEY_CODE)); Toast.makeText(getApplicationContext(), countryCode, Toast.LENGTH_SHORT).show();
// starts a new Intent to update/delete a Country // pass in row Id to create the Content URI for a single row Intent countryEdit = new Intent(getBaseContext(), CountryEdit.class); Bundle bundle = new Bundle(); bundle.putString("mode", "update"); bundle.putString("rowId", rowId); countryEdit.putExtras(bundle); startActivity(countryEdit);
} });
}
// This is called when a new Loader needs to be created. @Override public Loader onCreateLoader(int id, Bundle args) { String[] projection = { CountriesDb.KEY_ROWID, CountriesDb.KEY_CODE, CountriesDb.KEY_NAME, CountriesDb.KEY_CONTINENT}; CursorLoader cursorLoader = new CursorLoader(this, MyContentProvider.CONTENT_URI, projection, null, null, null); return cursorLoader; }
@Override public void onLoadFinished(Loader loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) dataAdapter.swapCursor(data); }
@Override public void onLoaderReset(Loader loader) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. dataAdapter.swapCursor(null); }
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.detail_page);
// get the values passed to the activity from the calling activity // determine the mode - add, update or delete if (this.getIntent().getExtras() != null){ Bundle bundle = this.getIntent().getExtras(); mode = bundle.getString("mode"); }
// get references to the buttons and attach listeners save = (Button) findViewById(R.id.save); save.setOnClickListener(this); delete = (Button) findViewById(R.id.delete); delete.setOnClickListener(this);
code = (EditText) findViewById(R.id.code); name = (EditText) findViewById(R.id.name);
// create a dropdown for users to select various continents continentList = (Spinner) findViewById(R.id.continentList); ArrayAdapter adapter = ArrayAdapter.createFromResource(this, R.array.continent_array, android.R.layout.simple_spinner_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); continentList.setAdapter(adapter);
// if in add mode disable the delete option if(mode.trim().equalsIgnoreCase("add")){ delete.setEnabled(false); } // get the rowId for the specific country else{ Bundle bundle = this.getIntent().getExtras(); id = bundle.getString("rowId"); loadCountryInfo(); }
}
public void onClick(View v) {
// get values from the spinner and the input text fields String myContinent = continentList.getSelectedItem().toString(); String myCode = code.getText().toString(); String myName = name.getText().toString();
// check for blanks if(myCode.trim().equalsIgnoreCase("")){ Toast.makeText(getBaseContext(), "Please ENTER country code", Toast.LENGTH_LONG).show(); return; }
// check for blanks if(myName.trim().equalsIgnoreCase("")){ Toast.makeText(getBaseContext(), "Please ENTER country name", Toast.LENGTH_LONG).show(); return; }
switch (v.getId()) { case R.id.save: ContentValues values = new ContentValues(); values.put(CountriesDb.KEY_CODE, myCode); values.put(CountriesDb.KEY_NAME, myName); values.put(CountriesDb.KEY_CONTINENT, myContinent);
// insert a record if(mode.trim().equalsIgnoreCase("add")){ getContentResolver().insert(MyContentProvider.CONTENT_URI, values); } // update a record else { Uri uri = Uri.parse(MyContentProvider.CONTENT_URI + "/" + id); getContentResolver().update(uri, values, null, null); } finish(); break;
case R.id.delete: // delete a record Uri uri = Uri.parse(MyContentProvider.CONTENT_URI + "/" + id); getContentResolver().delete(uri, null, null); finish(); break;
// More buttons go here (if any) ...
} }
// based on the rowId get all information from the Content Provider // about that country private void loadCountryInfo(){
ViewFlipper is a subclass of ViewAnimator that makes it easy to animate a bunch of views that are added to it. It only displays one child view at a time and you can also set it to rotate the views automatically. Just keep in mind that if you have duration set for your animation alpha then the flipper duration should be greater than that otherwise the view will change even before it completely comes into focus.
//get references to our buttons and toogle button myToggleButton = (ToggleButton) findViewById(R.id.toggleButton); previousView = (Button) findViewById(R.id.previousView); nextView = (Button) findViewById(R.id.nextView);
//attach onClick listeners to the buttons myToggleButton.setOnClickListener(this); previousView.setOnClickListener(this); nextView.setOnClickListener(this);
@Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.activity_main, menu); return true; }
@Override public void onClick(View v) {
//get reference to the view flipper ViewFlipper myViewFlipper = (ViewFlipper) findViewById(R.id.myViewFlipper); //set the animation for the view that enters the screen myViewFlipper.setInAnimation(slide_in_right); //set the animation for the view leaving th screen myViewFlipper.setOutAnimation(slide_out_left);
switch (v.getId()) { case R.id.nextView: //show the next view myViewFlipper.showNext(); break;
case R.id.previousView: //show the next view myViewFlipper.setInAnimation(slide_in_left); myViewFlipper.setOutAnimation(slide_out_right); myViewFlipper.showNext(); break;
case R.id.toggleButton:
if(myToggleButton.isChecked()){ //set flipper interval myViewFlipper.setFlipInterval(6000); //start flipping the views myViewFlipper.startFlipping(); } else{ //stop flipping the views myViewFlipper.stopFlipping(); } break;
public class AccelerometerListener implements SensorEventListener {
private SensorManager sensorManager; private List sensors; private Sensor sensor; private long lastUpdate = -1; private long currentTime = -1;
private float last_x, last_y, last_z; private float current_x, current_y, current_z, currenForce; private static final int FORCE_THRESHOLD = 900; private final int DATA_X = SensorManager.DATA_X; private final int DATA_Y = SensorManager.DATA_Y; private final int DATA_Z = SensorManager.DATA_Z;
In this example we are going to place a Checkbox inside the ListView row along with some Text. The concept is the same if you want to place a button or an image or all of them. The first issue that I faced when placing a Checkbox is the OnItemClickListener stopped working. To get around that you have to specify android:focusable="false" and android:focusableInTouchMode="false" for your checkbox view. Now to listen for the click event on the checkbox you have attach the OnClickListener when the you are inflating the custom layout inside your getView() method. To find out what all checkboxes are selected just loop thru the ArrayList that is being maintained by listening to the onClick event for the checkbox.
Source for Activity - ListViewCheckboxesActivity.java
public class ListViewCheckboxesActivity extends Activity {
MyCustomAdapter dataAdapter = null;
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);
//Generate list View from ArrayList displayListView();
checkButtonClick();
}
private void displayListView() {
//Array list of countries ArrayList countryList = new ArrayList(); Country country = new Country("AFG","Afghanistan",false); countryList.add(country); country = new Country("ALB","Albania",true); countryList.add(country); country = new Country("DZA","Algeria",false); countryList.add(country); country = new Country("ASM","American Samoa",true); countryList.add(country); country = new Country("AND","Andorra",true); countryList.add(country); country = new Country("AGO","Angola",false); countryList.add(country); country = new Country("AIA","Anguilla",false); countryList.add(country);
//create an ArrayAdaptar from the String Array dataAdapter = new MyCustomAdapter(this, R.layout.country_info, countryList); ListView listView = (ListView) findViewById(R.id.listView1); // Assign adapter to ListView listView.setAdapter(dataAdapter);
listView.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView parent, View view, int position, long id) { // When clicked, show a toast with the TextView text Country country = (Country) parent.getItemAtPosition(position); Toast.makeText(getApplicationContext(), "Clicked on Row: " + country.getName(), Toast.LENGTH_LONG).show(); } });
}
private class MyCustomAdapter extends ArrayAdapter {
private ArrayList countryList;
public MyCustomAdapter(Context context, int textViewResourceId, ArrayList countryList) { super(context, textViewResourceId, countryList); this.countryList = new ArrayList(); this.countryList.addAll(countryList); }
private class ViewHolder { TextView code; CheckBox name; }
@Override public View getView(int position, View convertView, ViewGroup parent) {
@Override public void onClick(View v) { StringBuffer responseText = new StringBuffer(); responseText.append("The following were selected...\n");
ArrayList countryList = dataAdapter.countryList; use for loop i=0 to < countryList.size(){ Country country = countryList.get(i); if(country.isSelected()){ responseText.append("\n" + country.getName()); } }
In this example we are going to learn how to create a SQLite database adapter that will perform the basic operations such as creating the table, upgrading the database as well as providing access to the data based on given input parameters. SQLiteDatabase has methods to create, delete, execute SQL commands, and perform other common database management tasks. To query a given table and return a Cursor over the result set you can use the following method.
A list of which columns to return. Passing null will return all columns, which is discouraged to prevent reading data from storage that isn't going to be used.
selection
A filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE itself). Passing null will return all rows for the given table.
selectionArgs
You may include ?s in selection, which will be replaced by the values from selectionArgs, in order that they appear in the selection. The values will be bound as Strings.
groupBy
A filter declaring how to group rows, formatted as an SQL GROUP BY clause (excluding the GROUP BY itself). Passing null will cause the rows to not be grouped.
having
A filter declare which row groups to include in the cursor, if row grouping is being used, formatted as an SQL HAVING clause (excluding the HAVING itself). Passing null will cause all row groups to be included, and is required when row grouping is not being used.
orderBy
How to order the rows, formatted as an SQL ORDER BY clause (excluding the ORDER BY itself). Passing null will use the default sort order, which may be unordered.
limit
Limits the number of rows returned by the query, formatted as LIMIT clause. Passing null denotes no LIMIT clause.
Returns
A Cursor object, which is positioned before the first entry. Note that Cursors are not synchronized
public static final String KEY_ROWID = "_id"; public static final String KEY_STATUS = "statusCode"; public static final String KEY_COMPANY = "company"; public static final String KEY_ORDER = "orderNumber"; public static final String KEY_SEQ = "sequence"; public static final String KEY_ITEM = "item"; public static final String KEY_DESCRIPTION = "description"; public static final String KEY_QUANTITY = "quantity"; public static final String KEY_PRICE = "price";
private static final String TAG = "OrderDetailDbAdapter"; private DatabaseHelper mDbHelper; private SQLiteDatabase mDb;
private static final String DATABASE_NAME = "OrderDatabase"; private static final String SQLITE_TABLE = "OrderDetail"; private static final int DATABASE_VERSION = 1;
@Override public void onCreate(SQLiteDatabase db) { Log.w(TAG, DATABASE_CREATE); db.execSQL(DATABASE_CREATE); }
@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data"); db.execSQL("DROP TABLE IF EXISTS " + SQLITE_TABLE); onCreate(db); } }
public OrderDetailDbAdapter(Context ctx) { this.mCtx = ctx; }
public OrderDetailDbAdapter open() throws SQLException { mDbHelper = new DatabaseHelper(mCtx); mDb = mDbHelper.getWritableDatabase(); return this; }
public void close() { if (mDbHelper != null) { mDbHelper.close(); } }