This is more of a "Am I doing this the best way" kind of question in regards to closing the connection to an sqlite database. What i've been doing is closing the database within my view activity in the onpause and ondestroy methods. When the user navigates back to the activity I requery the database on the onresume method. Here is a snippet of code:
private void setupView() {
newRecipeButton = (Button)findViewById(R.id.NewRecipeButton);
newRecipeButton.setOnClickListener(addNewRecipe);
list = (ListView)findViewById(R.id.RecipeList);
dbHelper = new DataBaseHelper(this);
setupDataBase();
database = dbHelper.getMyDataBase();
queryDataBase();
list.setEmptyView(findViewById(R.id.EmptyList));
registerForContextMenu(list);
}
private void queryDataBase() {
data = database.query("recipes", fields, null, null, null, null, null);
dataSource = new DataBaseAdapter(this, R.layout.recipe_list_item, data, fields, new int[] {R.id.RecipeName, R.id.RecipeStyle});
list.setAdapter(dataSource);
}
private void setupDataBase() {
//Create the database if this is the first run.
try{
dbHelper.createDataBase();
} catch(IOException e){
throw new Error("Unable to Create Database");
}
//Otherwise open the database.
try{
dbHelper.openDataBase();
}catch (SQLiteException e){
throw e;
}
}
#Override
protected void onResume(){
super.onResume();
setupView();
}
#Override
protected void onDestroy() {
super.onDestroy();
dbHelper.close();
}
Of course this isn't the entire activity but merely the parts that deal with the sqlite database. So am I doing this in a tidy manner or is there a better practice I should be following?
onDestroy is probably the best phase to place disposal method of any IO connection.
Another approach is using try { query() } finally { db.close(); } pattern – which also makes sense so that database is only activated during your query.
In Java 7, there is a new syntactic sugar try (db = open database();) { execute queries(); } that closes database automatically after executing queries finishes. However Java 7 is not available on Android devices yet.
Related
I'm migrating the following pattern of accessing the Android app's SQLite database into the RxJava world:
public List<Stuff> doStuff(){
synchronized (lock) {
open(); // this effectively checks for isOpen() then calls getWritableDatabase()
// query the database for stuff
close(); // SQLiteOpenHelper close method
return stuffList;
}
}
Something I'm struggling is when should I close the database connection? I know there are patterns for not closing the connection at all as well as closing the connection as part of the Activity method. However, those patterns would require me applying the logic to the whole database manager class which I'd like to avoid if possible. Was hoping maybe there's a suggested way to handle this with RxJava and specifically SqlBright wrapper? My migrated code looks something like this:
public Observable<List<Stuff>> doStuff(){
synchronized (lock) {
open();
String sql = <..>;
return db.createQuery(tableName, sql, args).mapToList(mStuffMapper);
// where do I close()?
}
}
The solution I'm after, ideally, should allow me to change this one method, keeping the rest with the current open/close pattern.
You can use Subscription to close the connection.
db.createQuery(tableName, sql, args)
.mapToList(mStuffMapper);
.doOnSubscribe(new Action0() {
#Override public void call() {
close();
}
});
Subscription subscribe = doStuff().subscribe();
subscribe.unsubscribe();
I am trying to change the code of the Searchable Dictionary example (full code here), so that I can modify "description.txt" and it will update it when I build the program from Eclipse to my application.
I think it doesn't take the modifications into account after the first time because the dictionary is already loaded, with this function:
private void loadDictionary() {
new Thread(new Runnable() {
public void run() {
try {
loadWords();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
}
Anybody knows how to change this, so that it always load the new "definition.txt" and that I can keep modifying it without having to reinstall the app all the time?
In this specific code, the solution was to increment the database version in DictionaryDatabase.java:
private static final int DATABASE_VERSION = 3;
I regularly get reports from users with this error.
Unable to getWritableDatabase.
try {
//dbOpenHelper is a standard SQLiteOpenHelper
dbOpenHelper.getWritableDatabase();
} catch (Exception e) {
//unable to connect to database.
return;
}
This is only happening sometimes. What could it be? Any fixes or workarounds?
There isn't much to go off of, need to see your LogCat to help any further. Are you closing your dbOpenHelper?
Here is a quick example:
public DatabaseControl open() throws SQLiteException {
dbHelper = new DatabaseHelper(context);
database = dbHelper.getWritableDatabase();
return this;
}
public void close() {
dbHelper.close();
}
Edit 1 - Again, this is my best guess since a LogCat is unobtainable. (In the end it might be in your best interest to run this application on your Dev device and try to replicate and then get your full LogCat ouput)
It sounds to me like the Activity that has the DB is being accessed and processed fine with a proper close(), but in some instances maybe instead of running through that Activity the user presses the back button where onBackPressed() maybe should have a DB close() call?
In other words, to troubleshoot, I would #Override Acitivity lifecycle methods: onPause(), onResume(), onStart(), onStop(), and onDestroy() as well as onBackPressed() simply adding the DB close() and test your application then. If you do this then, I would think that, any returning lifecycle, such as onStart() or onResume() should contain the DB getWritableDatabase()
According to the documentation (see below), there really is no way to get Unable to getWritableDatabase unless the database was not closed properly. Also make sure that if you change anything in your SQlite code / structure that you need to update the DB Version #.
public SQLiteDatabase getWritableDatabase ()
Create and/or open a database that will be used for reading and writing. The first time this is called, the database will be opened and onCreate(SQLiteDatabase), onUpgrade(SQLiteDatabase, int, int) and/or onOpen(SQLiteDatabase) will be called.
Once opened successfully, the database is cached, so you can call this method every time you need to write to the database. (Make sure to call close() when you no longer need the database.) Errors such as bad permissions or a full disk may cause this method to fail, but future attempts may succeed if the problem is fixed.
Edit 2
I should have shown how I called on the open() and close() methods the first time. Sorry about that. You don't need the DatabaseHelper open unless you are retrieving or posting to it, so immediately after processing close it until the next time you need it. Same goes for the Cursor, etc
Below is how I query the database, I open everything, retrieve the information, process the information and then immediately close everything. See below:
DatabaseControl control = new DatabaseControl(this);
try {
database = (new DatabaseHelper(this)).getWritableDatabase();
DatabaseControl control = new DatabaseControl(DbTestDisplay.this);
control.open();
control.fetchAllItems();
Cursor c = database.query(GlobalDBVars.TABLE_NAME, null, null, null, null, null, null, null);
c.moveToFirst();
int initialCount = c.getCount();
Log.i("cursor", "Initial Count = " + initialCount);
List<Integer> x = new ArrayList<Integer>();
if (initialCount > 0) {
for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) {
/* Process everything here */
} /* End for loop */
} /* End if statement */
control.close();
c.close();
database.close();
} catch (SQLException sqe) {
Log.e("fetchAllItems", "FAILED: " + sqe.getMessage() + " allData = ");
}
Try getReadableDatabase() instead of getWritableDatabase()
try {
//dbOpenHelper is a standard SQLiteOpenHelper
dbOpenHelper.getReadableDatabase();
} catch (Exception e) {
//unable to connect to database.
return;
}
I have listed of products with different category. I have to sort them. Because of the queries, It is taking more time to load. Between two activities, the screen is coming black. I want to run the query in the background. How can I do that and how to use its result in main activity?
private class InsertTask extends AsyncTask {
String cat;
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected Boolean doInBackground(String... params) {
Boolean success = false;
try {
category(cat);
success = true;
} catch (Exception e) {
if(e.getMessage()!=null)
e.printStackTrace();
}
return success;
}
#Override
protected void onPostExecute(Boolean success) {
super.onPostExecute(success);
}
private void category(String category) {
try{
Cursor1 = mDbHelper.fetchcategory(category);
}catch(Exception e){
Log.v("Excep", ""+e);
}
}
And when called
InsertTask task = new InsertTask();
task.execute();
I have listed the category in buttons. How can I get the values then?
You should use AsyncTask for that. And some more info.
Its good you have thought of AsyncTask. Firstly, you can declare this class as inner in you class activity (if you haven't previously did) and so you are able to access you view class members.
You can do this also by creating thread and one handler that will be used to update your UI components. Remember that if you use threads you'll need to lock/unlock your database object because of the thread safety(if any other thread is accessing the database for any reason). Read more about thread safety of dbs.
I was doing some searching myself, and I came across this read, its rather long but looks extremely helpful, with lots of code examples. (I bookmarked it for myself).
Threads, Async, and Handlers O MY!
But some form of threading is the ticket.
From Android dev.
(My favorite code snippet)
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
//Do Work here
}
}).start();
}
In my application, I am using an AsyncTask to write some data to a database in a transaction. This database is also accessed from the UI thread. While looking through the available database methods, I came across yieldIfContendedSafely(). It seems like this method should be used for any cases where a transaction is being made from a separate thread. But there is hardly any documentation on this method other than the following:
Temporarily end the transaction to let other threads run. The transaction is assumed to be successful so far. Do not call setTransactionSuccessful before calling this. When this returns a new transaction will have been created but not marked as successful. This assumes that there are no nested transactions (beginTransaction has only been called once) and will throw an exception if that is not the case.
Here is how I would assume that you would use this method from a thread:
try {
db.beginTransaction();
//insert some stuff into the database here
...
// is this how you use this method?
boolean yielded = db.yieldIfContendedSafely();
if (yielded) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
db.setTransactionSuccessful();
} catch (SQLException e) {
return false;
} finally {
db.endTransaction();
db.close();
}
Is this the correct way to use this method? Is it alright to use db.yieldIfContendedSafely() more than once in the same transaction, in between multiple writes to different tables in the database? Any suggestions?
Pulling some example code from the Android libraries it seems much simpler to use than that...
This is taken from com.android.providers.calendar.SQLiteContentProvider.java
#Override
public int bulkInsert(Uri uri, ContentValues[] values) {
int numValues = values.length;
mDb = mOpenHelper.getWritableDatabase();
mDb.beginTransactionWithListener(this);
try {
for (int i = 0; i < numValues; i++) {
Uri result = insertInTransaction(uri, values[i]);
if (result != null) {
mNotifyChange = true;
}
mDb.yieldIfContendedSafely();
}
mDb.setTransactionSuccessful();
} finally {
mDb.endTransaction();
}
onEndTransaction();
return numValues;
}
Also looking into the source code for the function itself, it seems that, if yielded, the call will defer execution of your thread for a short period in any case.