DBManager in Android application - android

I am using a DBManager layer, it holds as private member all all the SQLiteOpenHelpers for the tables. The class is as followed
public class DBManager
{
private static final String mTAG = "DBManager";
Context mContext = null;
DB1 mDB1 = null;
DB2 mDB2 = null;
public DBManager( Context context )
{
mContext = context;
mDB1 = new DB1( mContext );
mDB2 = new DB2( mContext );
}
#Override
protected void finalize() throws Throwable
{
Close();
super.finalize();
}
public void Close()
{
if( mDB1 != null ) mDB1.close();
if( mDB2 != null ) mDB2.close();
}
.... Public API towards the DB1/DB2....
}
The question is like this:
Currently I am using it in each activity I need the DB as a private member.
Maybe better to use it as singleton? Can I? If do - which context to pass?
Or any other way to use?
Thanks

Here's what I do. Keep one instance of SqliteOpenHelper per-database. Do not keep more than one Helper for a single database. This can cause issues with writes if more than one thread are trying to write at the same time.
Keep the Helper instance as a singleton across your application process. Multiple Activity and Service clients can access it easily and you won't have write lock issues.
See here for details:
http://www.touchlab.co/blog/single-sqlite-connection/
It links to a few blog posts of mine about Sqlite and multiple connections.

Related

Access SQLite database simultaneously from different threads

Here is my problem. My app starts several threads, each for a particular object to be updated. The update of the object happens with a query to a single database. There is a single database and a single OpenHelper. The behavior of my app suggests me that the calls to the database are non simultaneous as well as I would like. How can I access the same database from different threads simultaneously? If the data for each object are in different tables is more efficient to split the database in several databases, one for each object?
public class SomethingToBeUpdated implements Runnable {
private SQLiteDatabase db;
#Override
public void run() {
db.rawQuery( ... bla bla
}
}
public class MainActivity extends Activity {
private SomethingToBeUpdated[] list = bla bla...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
for( SomethingToBeUpdated x : list ) {
new Thread(x).start();
}
}
}
For the sake of accessing the database in various threads you need to have a Database manager which keeps an object of your database class and pass it to any thread that needs it. In android you cannot access database simultaneously in several threads with different objects. It may just block your UI (the problem i was facing a few days ago).
So to overcome this problem you can use the the database manager i used which is defined as follows:
public class DatabaseManager {
private AtomicInteger mOpenCounter = new AtomicInteger();
private static DatabaseManager instance;
private static SQLiteOpenHelper mDatabaseHelper;
private SQLiteDatabase mDatabase;
public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
if (instance == null) {
instance = new DatabaseManager();
mDatabaseHelper = helper;
}
}
public static synchronized DatabaseManager getInstance() {
if (instance == null) {
throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
" is not initialized, call initializeInstance(..) method first.");
}
return instance;
}
public synchronized SQLiteDatabase openDatabase() {
if(mOpenCounter.incrementAndGet() == 1) {
mDatabase = mDatabaseHelper.getWritableDatabase();
}
return mDatabase;
}
public synchronized void closeDatabase() {
if(mOpenCounter.decrementAndGet() == 0) {
mDatabase.close();
}
}
}
Then you initialize it once like this:
DatabaseManager.initializeInstance(new ChatSQL(c));
And then you can get the database object wherever you want with this syntax:
SQLiteDatabase db = DatabaseManager.getInstance().openDatabase(); //in your methods which are querying the database
With this method your database is now thread safe. Hope this helps.
If the data for each object are in different tables is more efficient to split the database in several databases, one for each object?
No it is not efficient. It has a lot of overheads to define, access, make object and query different databases. And what if you want to join tables? you just cannot.

Passing a null context to a database helper

I have a static function that gets called whenever my background service gets a new location. In this function I want to take to coordinates passed in and save them in my database. Can I pass 'null' as the context to create an instance of the database helper or is there a better way to do this. Thanks.
public static void locationHasChanged() {
final wd_DatabaseHelper helper = new wd_DatabaseHelper(null, "myDB.db", null, 1);
}
Probably not. Usually your Database helper extends SQLiteOpenHelper and the context will be used to call the openOrCreateDatabase() or the getDatabasePath(). I can't say for sure without seeing the code of wd_DatabaseHelper but having a null context is never a good idea. See for your self ... Source of SQLiteOpenHelper
since an android Service is a context you can pass "this of the service" into your method
public class MyLocationHelper {
public static void locationHasChanged(Context context) {
final wd_DatabaseHelper helper = new wd_DatabaseHelper(context, "myDB.db", null, 1);
....
}
}
public class MyService extends Service {
private void onLocationHasChanged()
{
MyLocationHelper.locationHasChanged(this);
}
}

Sharing database connection

I just need some validation that this is a workable way to share a SQLlite database connection across my Activities and Services of my app, by using a helper class:
public class DatabaseHelper
{
private static DBItems dbItems;
private static SQLiteDatabase sdbItemsRead, sbItemsWrite;
public static synchronized DBItems getHelper(Context context)
{
if (dbItems == null)
dbItems = new DBItems(context);
return dbItems;
}
public static synchronized SQLiteDatabase select(Context context)
{
if (sdbItemsRead == null)
sdbItemsRead = getHelper(context).getReadableDatabase();
return sdbItemsRead;
}
public static synchronized SQLiteDatabase write(Context context)
{
if (sbItemsWrite == null)
sbItemsWrite = getHelper(context).getWritableDatabase();
return sbItemsWrite;
}
}
In my code I'm calling this to select data:
DatabaseHelper.select(this).rawQuery("SELECT * FROM TABLE");
and this to write data:
DatabaseHelper.write(this).update("tbl_items", cv, "[id] = ?", new String[] { itemId.toString() });
I need to do this because I'm calling the database from different threads (UI and background) and I was getting "database is locked" exceptions thrown.
If you are creating database and calling in some activities then it will work fine, but if you want that your database should run along with service then it is always better to write the database in custom Content Provider.
Custom Content Provider will help you to run your database in background along with service.
you can refer the custom content provider from this link.

openOrCreateDatabase in different class causes null pointer exception

I have defined a new class to use in my project and I get null pointer exception in the context
Here is what i coded :
public class OurClass extends Activity {
private dha mContext;
private dhaService sContext;
public OurClass(dha dha) {
sContext=null;
mContext = dha;
}
public OurClass(dhaService dhx) {
sContext = dhx;
mContext=null;
}
public void put_default_value( String varname, String value) {
Log.i("dha", "d1");
SQLiteDatabase db;
Log.i("dha", "d1.5");
if (mContext==null) {
Log.i("dha", "dx1");
db = sContext.openOrCreateDatabase("gipi.db", SQLiteDatabase.CREATE_IF_NECESSARY,null);
Log.i("dha", "dx2");
} else {
Log.i("dha", "dz1");
db = android.database.sqlite.SQLiteDatabase.openOrCreateDatabase("gipi.db", null);
Log.i("dha", "dz2");
}
You're not actually checking that the Contexts are valid before using them.
For example, if OurClass(dhaService dhx) is called with a null Context, then putDefaultValue will fail with a NullPointerException because sContext is assigned the value of dhx without checking either dhx or sContext for valid values.
On a side note, neither sContext, nor mContext are being initialised, which means that with the lack of proper checking you have right now, you could easily inadvertently call put_default_value, while never actually having called either of the constructors. This is not best practice.
EDIT:
One way to help guard against this is to make the default constructor private, so that an instance of the class must be created by calling one of the constructors you have provided:
private:
OurClass ( void ) {
}
Even if you do that, you still need to properly validate your inputs to at least detect null pointers - you can't just assume it will all work as you intend, since it clearly isn't right now.

android SQLiteOpenHelper and Leaks

I wanna make SQLiteOpenHelper with some additional methods (like getStreetsCursor) which returns data from my db. So i wrote something like this:
public class DBHelper extends SQLiteOpenHelper {
public static final String DB_NAME="some.db";
public static final String T1_NAME="streets";
public static final String T1_FNAME1="name";
public static final String T2_NAME="addresses";
public static final String T2_FNAME1="name";
public static final String T2_FNAME2="address";
private Context appContext;
public DBHelper(Context context) {
super(context, DB_NAME, null, 1);
appContext=context;
}
public Cursor getStreetsCursor(String chars) {
SQLiteDatabase dbReadable=this.getReadableDatabase();
Cursor curStreets = dbReadable.query(DBHelper.T1_NAME,
new String[] {"_id",DBHelper.T1_FNAME1},
DBHelper.T1_FNAME1+" LIKE(\""+chars.toUpperCase()+"%\")",
null, null, null, DBHelper.T1_FNAME1);
return curStreets;
}
There are several methods like getStreetsCursor (getAddresses, getAddress4 etc) defined in DBHelper.
I guess if it is a DB Helper it definitely should have such methods i mean the DBHelper is a logical placeholder for them.
What i do in the activity is create a new DBHelper instance and store it in private field (called mDBHelper) of activity. Additionally in onDestroy method of activity i have mDBHelper.close().
private DBHelper mDBHelper;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDBHelper = new DBHelper(this);
...
#Override
protected void onDestroy() {
if (mDBHelper!=null){
mDBHelper.close();
Log.i(APP_TAG,"mDBHelper.close() in "+this.getClass());
}
super.onDestroy();
}
Those activities uses the mDBHelper only in one way - by calling it's custom methods like mDBHelper.getStreetsCursor().
Eventually I found an exception message in logcat about my app got leaks in such activities which uses DBHelper. It says something like "database was never closed". So i decided to add a call to close() method in each of my custom methods just before return. So it looks like:
public Cursor getStreetsCursor(String chars) {
SQLiteDatabase dbReadable=this.getReadableDatabase();
Cursor curStreets = dbReadable.query(DBHelper.T1_NAME,
new String[] {"_id",DBHelper.T1_FNAME1},
DBHelper.T1_FNAME1+" LIKE(\""+chars.toUpperCase()+"%\")",
null, null, null, DBHelper.T1_FNAME1);
dbReadable.close();
return curStreets;
}
Now i got no leaks but got the next problem - only first call to mDBHelper.getStreetsCursor() really executes. All of the next calls returns null. Thats due to dbReadable.close(); line. If i remove it everythig works fine but i got leaks again.
So i cant figure out whats going wrong. In every custom method i got SQLiteDatabase dbReadable=this.getReadableDatabase(); line which should return a readable instance but after executing the close() method it doesn't.
I guess its about my custom methods because they calls .getReadableDatabase() inside an instance of DBHelper. If i place those methods directly in activity everything works fine - no leak exceptions and every time methods returns a proper data. But i want to place those methods in my DBHelper class.
So the main question is - what's wrong and how to do that properly?
You shouldn't have a SQLiteDatabase dbReadable=this.getReadableDatabase();
in every method getSomethingCursor as requesting a database object is expensive ( I think I read it in SO).
So you can create the object from your constructor
SQLiteDatabase dbReadable;
public DBHelper(Context context) {
super(context, DB_NAME, null, 1);
appContext=context;
dbReadable=this.getReadableDatabase()
}
public Cursor getStreetsCursor(String chars) {
Cursor curStreets = dbReadable.query(DBHelper.T1_NAME,
new String[] {"_id",DBHelper.T1_FNAME1},
DBHelper.T1_FNAME1+" LIKE(\""+chars.toUpperCase()+"%\")",
null, null, null, DBHelper.T1_FNAME1);
return curStreets;
}
Create a method to close the database handle:
public closeDb() {
if (dbReadable != null) { dbReadable.close();}
}
And in you activity:
#Override
protected void onDestroy() {
if (mDBHelper!=null){
mDBHelper.closeDb();
Log.i(APP_TAG,"mDBHelper.close() in "+this.getClass());
}
super.onDestroy();
}
And use startManagingCursor (if SDK < HoneyComb) to let your activity manage your cursor (closing it on onDestroy for example
Make SQLiteDatabase dbReadable a class member, and implement
public void close()
{
dbReadable.close();
}
Then, when you call mDBHelper.close(); in your activity's onDestroy() function, it should be ok.
Make a new function
public void DBclose()
{
curstreets.close();
dbReadable.close();
}
Then you can call this in your activity in onDestroy() fucntion.
Years after... I just want to state that now I'll better use Realm.io DB engine and I do it now in my projects. As for my old question I also want to notice that using getReadableDatabase() is a bad idea since across devices fragmentation I met some curios sqlite bug - readable instance will be closed automatically right after a query execution which is unexpected behavior. So I recommend always use getWritableDatabase() method even for reading purpose. And finally I end up with Dmytro Danylyk solution - give it a try, Dmytro is Google Developer Expert:
public class DatabaseManager {
private AtomicInteger mOpenCounter = new AtomicInteger();
private static DatabaseManager instance;
private static SQLiteOpenHelper mDatabaseHelper;
private SQLiteDatabase mDatabase;
public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
if (instance == null) {
instance = new DatabaseManager();
mDatabaseHelper = helper;
}
}
public static synchronized DatabaseManager getInstance() {
if (instance == null) {
throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
" is not initialized, call initializeInstance(..) method first.");
}
return instance;
}
public synchronized SQLiteDatabase openDatabase() {
if(mOpenCounter.incrementAndGet() == 1) {
// Opening new database
mDatabase = mDatabaseHelper.getWritableDatabase();
}
return mDatabase;
}
public synchronized void closeDatabase() {
if(mOpenCounter.decrementAndGet() == 0) {
// Closing database
mDatabase.close();
}
}
}
And use it as follows.
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way

Categories

Resources