5

I'm planning on improving my SQLite implementation - currently, activities are opening the SQLite DB whenever they need some relevant data, then closing it. Closing the DB is important to avoid exceptions.

my design goals:

  1. thread-safe access to an applications' SQLite DB

  2. synchronous operation

What I thought I would do is implement some kind of "Manager" class instead of my basic "SQLhelper" class. I want synchronous operation so that rules out implementing it as a service with messages.

I think the best way to implement this "SQLiteManager" is as a singleton.

Are there any better implementations ?

Someone Somewhere
  • 23,475
  • 11
  • 118
  • 166
  • [ContentProvider](http://developer.android.com/reference/android/content/ContentProvider.html) – Selvin Apr 26 '11 at 09:12
  • ContentProvider is seemingly overkill for this app since I don't need to share across other apps. I was inspired by the following to make my own solution (I'll post a skeleton of the solution soon) http://stackoverflow.com/questions/987072/using-application-context-everywhere – Someone Somewhere May 04 '11 at 23:25

1 Answers1

10

Step 1 - extend the Application class

import android.app.Application;
import android.content.Context;

/**
 * This class is created automatically when the app launches.
 * It is used to provide an application-level context for the SQLiteOpenHelper
 */
public class ApplicationContext extends Application
{

    private static ApplicationContext instance;

    public ApplicationContext()
    {
        instance = this;
    }

    public static Context getContext()
    {
        return instance;
    }

}

Step 2 - update the manifest so that this application class is used

<application android:name="ApplicationContext"
             android:icon="@drawable/icon" 
             android:label="@string/app_name"
             android:debuggable="true">

Step 3 - build the singleton SQLdataHelper into your app

public class SQLdataHelper
{
    //for logging
    private final String TAG = this.getClass().getSimpleName();

    //DATABASE
    private static final String DATABASE_NAME = "my.db";
    private static final int DATABASE_VERSION = 1;//initial version

    //TABLE NAMES
    private static final String TABLE_NAME_A = "exampleOneTable";

    //MEMBER VARIABLES
    private DatabaseHelper mDBhelper;
    private SQLiteDatabase mDB;

    //SINGLETON
    private static final SQLdataHelper instance = new SQLdataHelper();


    private SQLdataHelper()
    {
        final DatabaseHelper dbHelper = new DatabaseHelper(ApplicationContext.getContext());

        //open the DB for read and write
        mDB = dbHelper.getWritableDatabase();
    }


    public static SQLdataHelper getInstance()
    {
        return instance;
    }

    /**
     *  INSERT FUNCTIONS consisting of "synchronized" methods 
     */
    public synchronized long insertTableA(String myName, int myAge)
    {
        Long lValueToReturn;

        //organize the data to store as key/value pairs
        ContentValues kvPairs = new ContentValues();
        kvPairs.put("ColumnOne", myName);
        kvPairs.put("ColumnTwo", myAge);

        lValueToReturn = mDB.insert(TABLE_NAME_A, null, kvPairs);

        return lValueToReturn;
    }


    private static class DatabaseHelper extends SQLiteOpenHelper
    {

        DatabaseHelper(Context context)
        {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }


        //this is called for first time db is created.
        // put all CREATE TABLE here
        @Override
        public void onCreate(SQLiteDatabase db)
        {
            db.execSQL( "CREATE TABLE "
                       + TABLE_NAME_A 
                       + " ("
                       + "_id INTEGER PRIMARY KEY AUTOINCREMENT,"
                       + "ColumnOne TEXT,"
                       + "ColumnTwo TEXT"
                       + ")" );
        }

        //this is called when an existing user updates to a newer version of the app
        // add CREATE TABLE and ALTER TABLE here
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
        {

            //update SQL DB with new columns depending on old version
            // also add new tables
            //NOTE: whatever is done here must also go into onCreate() so that new users get the correct db created
            switch(oldVersion)
            {
            case 1:
//EXAMPLE         db.execSQL("ALTER TABLE " + TABLE_NAME_A + " ADD COLUMN ColumnThree INTEGER;");

                 //don't use a break. for next case simply let them run together to update all the way to latest version
                 //This way, the case just represents a starting point to start updating.

            case 2:
//EXAMPLE         db.execSQL("ALTER TABLE " + TABLE_NAME_A + " ADD COLUMN ColumnFour INTEGER;");

            }

//this code drops the table and will create a fresh one. Note all data lost!
//          db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME_C);
//          onCreate(db);
        }

    }

}

I've only included one example insert operation. Add more as you need them and simply make sure they are 'synchronized' methods.

Step 4 - use the SQLdataHelper in your activity

    SQLdataHelper mDataHelper = SQLdataHelper.getInstance();
    mDataHelper.insertTableA("Someone", 100);
Someone Somewhere
  • 23,475
  • 11
  • 118
  • 166
  • Do the methods need to be syncronized? I thought the database was thread safe? – Jon Wells Dec 12 '11 at 14:48
  • 1
    I've read that the SQLite DB is thread safe but I've experienced problems without using synchronized. – Someone Somewhere Dec 14 '11 at 18:15
  • Is correct to add this method to SQLdataHelper class? protected void finalize() throws Throwable { try { mDatabaseHelper.close(); } finally { super.finalize(); } } – petrnohejl Jan 29 '12 at 17:59
  • With my implementation the singleton lives for the life of the application. Therefore I did not implement an explicit destructor, instead, in the app shutdown code (or crash handler) the database is explicitly closed. See `Thread.setDefaultUncaughtExceptionHandler()` which I used in `ApplicationContext.onCreate()` after I posted my sample code. – Someone Somewhere Jan 31 '12 at 19:54
  • Is this approach still working in nowadays? I mean may be you have some improvements to it? – Prizoff Jul 19 '12 at 11:24
  • This still works well. My only recommendation is: for your 'select' statements, be sure to chunk the data up so that the responses don't get too large. This is done with 'limit', 'offset', and 'order by' – Someone Somewhere Jul 19 '12 at 21:02
  • @Someone, can you explain me how use setDefaultUncaughtExceptionHandler() for implicit closing of database connection? – GVillani82 May 02 '13 at 09:29
  • Joseph - here is a link to using the DefaultUncaughtExceptionHandler: http://www.intertech.com/Blog/android-handling-the-unexpected/ and within that handler, make a call to `SQLdataHelper.getInstance().closeDB();` be sure to implement `SQLdataHelper.closeDB()` by using `SQLiteOpenHelper.close();` method. The only doubt I have is what happens when the app crashes whilst doing a write to the DB. Maybe implement closeDB() as a synchronized method ? – Someone Somewhere Apr 07 '14 at 19:45