How to implement SQLiteOpenHelper with multiple DAO classes - android

I am trying to work out how best to implement an SQLite database in my Android app. Advice from this answer suggests using SQLiteOPenHelper, which I already have, but recommends only having 1 instance of it.
In my app I have an abstract BaseDAO class which extends SQLiteOpenHelper and creates the database tables if necessary. E.g:
public abstract class BaseDAO extends SQLiteOpenHelper{
public BaseDAO(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
#Override
public void onCreate(SQLiteDatabase db) {
String CREATE_TABLE = "CREATE TABLE " + ...
db.execSQL(CREATE_TABLE);
}
I have other DAO classes set up to access the database, which all extend this BaseDAO class, e.g. a MessageDAO class for putting message objects into the db.
public class MessageDAO extends BaseDAO {
public MessageDAO(Context context) {
super(context, DBContract.DB_NAME, null, DBContract.DB_VERSION);
}
public long addMessage(Message msg) throws Exception {
ContentValues values = new ContentValues();
values.put(DBContract.MessagesTable.COLUMN_TEXT, msg.getText());
values.put(DBContract.MessagesTable.COLUMN_FILE, msg.getFile());
values.put(DBContract.MessagesTable.COLUMN_TYPE, msg.getType());
values.put(DBContract.MessagesTable.COLUMN_TIMESTAMP, msg.getTimeStamp());
SQLiteDatabase db = this.getWritableDatabase();
long rowId = db.insert(DBContract.MessagesTable.TABLE_NAME, null, values);
db.close();
if (!(rowId > 0)) {
throw new Exception("Error inserting message into DB");
}else{
return rowId;
}
}
}
And a ContactDAO class to put Contact objects into the db:
public class ContactDAO extends BaseDAO {
public ContactDAO(Context context) {
super(context, DBContract.DB_NAME, null, DBContract.DB_VERSION);
}
public long addContact(Contact contact) throws Exception {
ContentValues values = new ContentValues();
values.put(DBContract.ContactTable._ID, contact.getId());
values.put(DBContract.ContactTable.COLUMN_CONTACT_NAME, contact.getContactName());
values.put(DBContract.ContactTable.COLUMN_CONTACT_TYPE, contact.getContactType());
SQLiteDatabase db = this.getWritableDatabase();
long rowId = db.insert(DBContract.ContactTable.TABLE_NAME, null, values);
db.close();
if (rowId <= 0) {
throw new Exception("Error inserting contact into DB");
} else {
return rowId;
}
}
}
My question is: should I refactor this so that all my DAO classes are put together into one class (and make that one class a singleton), or is this setup generally OK? It's really hard to find the right information on how to do this online as there seems to be multiple ways to handle db's in Android.

Too much OOP. There is no reason that all your DAO classes should derive from the open helperclass.
Your DAO classes correspond more or less to tables, so there is a N:1 relationship between DAO classes and the database object.
This means that the DAO classes should use a common instance of the open helper class (or that everything should be put into a single class, but you probably don't want that).

Related

inserting data without getWritableDatabase()?

I'm practicing android, I was able to insert using db.insert() without the use of getWritableDatabase() why is that? I thought we need db = getWritableDatabase(); before we can insert to the database
private SQLiteDatabase db;
#Override
public void onCreate(SQLiteDatabase db) {
this.db = db;
//some table creation
db.execSQL(TABLE);
fillQuestionsTable();
}
private void insertQuestion(Question question){
ContentValues cv = new ContentValues();
//some code
db.insert(QuestionsTable.TABLE_NAME, null, cv);
}
I thought it should be like this?
private void insertQuestion(Question question){
db = getWritableDatabase();
ContentValues cv = new ContentValues();
//some code
db.insert(QuestionsTable.TABLE_NAME, null, cv);
}
here's my implementation:
private void fillQuestionsTable(){
Question q1 = new Question("Programming, Easy: A is correct", "A", "B", "C",
1, Question.DIFFICULTY_EASY, Category.PROGRAMMING);
insertQuestion(q1);
}
your code works, because you are storing reference to db object in onCreate and then re-using this reference further. that should work in most of common cases, but getWritableDatabase() and getReadableDatabase() methods are there for a reason - you should use them in your insert/update/remove methods just like you posted. For safer, more reliable access in future app releases when there may/probably will be more code, more tables and more complicated lifecycle of app. and get rid of private SQLiteDatabase db; declaration on top of your SQLiteOpenHelper, use only locally fetched (with above methods) database object

getWritableDatabase() instance throughout the application lifecycle

I have made Singelton object to make queries to SQLiteOpenHelper in which I have saved instance of getWriteableDatabase(). Throughout the application lifecycle, for all select and insert/update queries I am using that instance from multiple IntentServices. I have read Write-ahead Logging (WAL) which supports concurrent execution of queries. I am using the above instance with WAL disabled. Actually at a point the database does not return data, I was wondering if SQLite file can get corrupted because I am using getWritableabledatabse for reading/writing from multiple intent services.
Can a deadlock occur with this approach?
As per my findings, WAL should be enabled if you are accessing database from multiple threads.
EDIT
DatabaseAdapter.java
public class DatabaseAdapter {
private Context mContext;
private SQLiteDatabase mSqLiteDatabase;
private DatabaseHelper mDbHelper;
private static DatabaseAdapter adapter;
public static DatabaseAdapter getInstance() {
if(adapter == null) {
synchronized (DatabaseAdapter.class) {
if(adapter == null)
adapter = new DatabaseAdapter(MyApp.getInstance());
}
}
return adapter;
}
public DatabaseHelper getDatabaseHelper() {
return mDbHelper;
}
private DatabaseAdapter(Context c) {
mContext = c;
mDbHelper = new DatabaseHelper(mContext);
mSqLiteDatabase = mDbHelper.getWritableDatabase();
}
private long insert(String tableName, ContentValues contentValues) throws Exception {
this.open();
long id = mSqLiteDatabase.insert(tableName, null, contentValues);
this.close();
return id;
}
private int update(String tableName, ContentValues contentValues, int pk_id) throws Exception {
this.open();
String whereClause = mDbHelper.pk_id + " = " + pk_id;
int n = mSqLiteDatabase.update(tableName, contentValues, whereClause, null);
this.close();
return n;
}
private ArrayList<MyObject> selectChallans(String whereClause, String orderby) throws Exception {
try {
ArrayList<MyObject> arrayListObjects = new ArrayList<MyObject>();
Cursor queryCursor = mSqLiteDatabase.query(tableName, null, whereClause, null, null, null, orderby, null);
if (queryCursor == null) {
return null;
}
while (queryCursor.moveToNext()) {
MyObject myobject = getMyObject(queryCursor);
if(myobject != null)
arrayListObjects.add(myobject);
}
queryCursor.close();
return arrayListObjects;
} catch (Exception e) {
e.printStackTrace();
this.forceClose();
throw e;
}
}
}
I am using this Adapter singleton instance through the application for insert/update and select queries. I was concerned about mSqLiteDatabase instance. These functions are being called from multiple IntentServices.
AFAIK, the best practice is calling getWritableabledatabse only once with one SQLiteOpenHelper. After that, you can use the returned database for all thread without any issue. You have to make sure that you are using one database connection. You can check this Good Answer for more detail.
The SqliteOpenHelper object holds on to one database connection. It appears to offer you a read and write connection, but it really doesn't. Call the read-only, and you'll get the write database connection regardless.
So, one helper instance, one db connection. Even if you use it from multiple threads, one connection at a time. The SqliteDatabase object uses java locks to keep access serialized. So, if 100 threads have one db instance, calls to the actual on-disk database are serialized.
So, one helper, one db connection, which is serialized in java code. One thread, 1000 threads, if you use one helper instance shared between them, all of your db access code is serial. And life is good (ish).
For me, I usually create and open the SQLiteOpenHelper in Application class, then I can use it everywhere in any thread in my app.

Best practice to implement method getXXX from db using cursor for Android

For example code below. Do we need to close cursor? Do we better use try/catch/finally instead of using if()?
public int getCount() {
final Cursor countCursor = contentResolver.query(
AnalyticContract.CONTENT_URI,
new String[] {"count(*) AS count"},
null,
null,
null);
if (countCursor == null) {
return 0;
}
countCursor.moveToFirst();
final int count = countCursor.getInt(0);
return count;
}
The try-with-resources statement is a try statement that declares one or more resources. A resource is an object that must be closed after the program is finished with it. The try-with-resources statement ensures that each resource is closed at the end of the statement. Any object that implements java.lang.AutoCloseable, which includes all objects which implement java.io.Closeable, can be used as a resource.
https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
The answer I believe is primarily opinion-based. It depends I guess on coder's preference and the circumstances.
I have always preferred the if (cursor != null) or vice versa approach. Unless something truly spectacular has happened; which will be handled by throws Exception, I'd use if-else checks wherever I want the reader/reviewer to see which parts are really and truly exceptions and which are occurrences of different possible/valid scenarios.
This brings us to the current problem of Curosr and applying null checks.
AFAIK (since mostly a Cursor is related with a SQLiteDatabase) a ContentResolver.query() should never return a null Cursor if the query itself is valid unless in case of an invalid query which is a real exception and you should instead get an Exception.
So in my opinion the best approach would be using your example either
public int getCount() throws Exception {
Cursor countCursor;
try {
countCursor = contentResolver.query(
AnalyticContract.CONTENT_URI,
new String[] {"count(*) AS count"},
null,
null,
null);
countCursor.moveToFirst();
return countCursor.getInt(0);
}
finally {
cursor.close();
}
}
Or a variation where Exception is caught and handled within the method itself.
Now to answer your second question whether or not you should close() a Cursor: you should always close a Cursor. Whenever you don't have need for it. If you delve deeper into any of the Cursor.close() method-implementations. Since Curosr is an interface which in case of SQLite is implemented by SQLiteCursor you will notice that this method releases any and all allocations held by it.
I prefer to make a database helper class and through that database access becomes much much easier. Sample of a database helper Class -
public class DatabaseHelperClass extends SQLiteOpenHelper {
public DatabaseHelperClass(Context context)
{
super(context,"hakeem.db",null,1);
}
//Tables
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("Your SQL Query to create a table");
}
//Delete Tables
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("drop table table_name if exists");
onCreate(db);
}
//Insertion of data into tables
long insertData(Various Parameters you like to pass for insertion)
{
SQLiteDatabase db=getWritableDatabase();
ContentValues values=new ContentValues();
values.put("col_name1",value);
values.put("col_name2",value);
values.put("col_name3",value);
values.put("col_name4",value);
return db.insert("signup_details",null,values);
}
//Delete record
public int deleteData(int id)
{
SQLiteDatabase sb=getWritableDatabase();
return sb.delete("hospital_details","id="+id,null);
}
//Update data in table
int updateData(Parameters you want to pass for update. Make sure you include a primary key)
{
ContentValues values=new ContentValues();
values.put("col_name1",value);
values.put("col_name2",value);
values.put("col_name3",value);
values.put("col_name4",value);
return getWritableDatabase().update("signup_details",values,"id="+id,null);
}
//Read data from tables
Cursor getSigninDetails() { return getWritableDatabase().rawQuery("select * from table_name",null); }
}
and to access results from the database-
private void getDataFromDatabase() {
Cursor cursor = db.getUserData();
if (cursor.moveToFirst()) {
do {
var_name1= cursor.getString(0);
var_name2= cursor.getString(1);
var_name3= cursor.getString(2);
var_name4= cursor.getString(3);
} while (cursor.moveToNext());
}
cursor.close();
}

Fetch data from existing sqlite database

I am having an existing sqlite database. I am developing an android app and I want to connect it with this existing sqlite DataBase.
Problem 1:
I have already included the sqlite database in my project via "DDMS push database function" as per my instructor's advise. Now I want to fetch the data from database, do I need to use SQLiteOpenHelper. If yes, how to use it and what will be coded in onCreate(SQLiteDatabase db) function and onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) function as we already have the Database with us, we don't really need to create it.
Problem 2:
What should be done to simply fetch the required data from the existing database.
Being a newbie, I am quite confused, can someone please explain these concepts and guide me to overcome this issue. Any help would be appreciated.
I have seen a tutorial also for this purpose as sugggested by #TronicZomB, but according to this tutorial (http://www.reigndesign.com/blog/using-your-own-sqlite-database-in-android-applications/), I must be having all the tables with primary key field as _id.
I have 7 tables namely destinations, events, tour, tour_cat, tour_dest, android_metadata and sqlite_sequence. Out of all, only tour_dest is not fulfilling the conditions of having a primary key named as _id. How to figure out this one?
Following is the screenshot of table which is lacking the primary key field necessary for binding id fields of database tables.
The onCreate and onUpgrade methods will be empty since you already have the database. There is a great tutorial on how to achieve this here.
You could then access the database like such (example):
public ArrayList<String> getValues(String table) {
ArrayList<String> values = new ArrayList<String>();
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT value FROM " + table, null);
if(cursor.moveToFirst()) {
do {
values.add(cursor.getString(cursor.getColumnIndex("value")));
}while(cursor.moveToNext());
}
cursor.close();
db.close();
return values;
}
Unless you are very comfortable with queries, databases, etc. I highly recommend you use http://satyan.github.io/sugar/ , it will also remove a lot of the boiler plate code required to do sqlite in Android
1. If DB already exists, onCreate will not invoke. onUpgrade will be invoked only if you will change DB version. onUpgrade you should to use if there some changes in your APP's database, and you have to make migration on new structure of data smoothly.
public class DbInit extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "name";
private static final int DATABASE_VERSION = 3;
private static final String DATABASE_CREATE = "create table connections . .. . ...
public DbInit(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
#Override
public void onCreate(SQLiteDatabase database) {
database.execSQL(DATABASE_CREATE);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (isChangeFromToVersion(1, 2, oldVersion, newVersion)) {
//Execute UPDATE here
}
}
private boolean isChangeFromToVersion(int from, int to, int oldVersion, int newVersion ) {
return (from == oldVersion && to == newVersion);
}
....
2. Simple example how to open connection to DB and get cursor object.
public class DAO {
private SQLiteDatabase database;
private DbInit dbHelper;
public ConnectionDAO(Context context) {
dbHelper = new DbInit(context);
}
public void open() throws SQLException {
database = dbHelper.getWritableDatabase();
}
public Connection getConnectionById(long id) {
Cursor cursor = null;
try {
open();
cursor = database.query(DbInit.TABLE_CONNECTIONS, allColumns, DbInit.COLUMN_ID + " = '" + id + "'", null, null, null, null);
if (!cursor.moveToFirst())
return null;
return cursorToConnection(cursor);
} finally {
if (cursor != null)
cursor.close();
close();
}
}
private Connection cursorToConnection(Cursor cursor) {
Connection connection = new Connection();
connection.setId(cursor.isNull(0) ? null : cursor.getInt(0));
connection.setName(cursor.isNull(1) ? null : cursor.getString(1));
.....
.....
return connection;
}

Storing application database schema

In example apps database is in most cases single table, so db schema is stored in static variable.
Storing large schema in seperate file is more friendly for me.
How can I do that? I thought about using resources (R.strings.db_schema) but probably there is a better way.
Could somebody give me any advice?
You could put the schema data in a raw file under res/raw. Then you can just load and parse that file the first time.
The way I do is to have a class per table, named after the table with "Table" suffix (e.g. PlayerTable or EventTable).
These classes contain all the static variable for the table name and all the field names, and they also contain two static methods:
public static void onCreate(SQLiteDatabase database)
public static void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion)
So that my SQLiteOpenHelper can just call all of them, without having hundreds of static variables with all the fields and create queries. E.g:
#Override
public void onCreate(SQLiteDatabase database) {
PlayerTable.onCreate(database);
EventTables.onCreate(database);
..... any other table you have .....
}
This class is then injected into all my data access objects (select / update / insert queries). For them I have dedicated classes that contain all my methods, by functionality (e.g. EventHandlingDAO for all the queries that deal with event handling).
And finally, theses DAO are injected into the activities that need them, when needed.
EDIT: A few more details about my code:
My main objects are the DAO (data access objects), in which I have methods like:
// in EventHandlingDAO:
public void addEvent(Event event) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
try {
database.execSQL("INSERT INTO " + EventTable.EVENT_TABLE_NAME + " (...."); // list of fields and values
} finally {
database.close();
}
}
public List<Event> getAllEvents() {
final List<Event> result = new ArrayList<Event>();
SQLiteDatabase database = databaseHelper.getReadableDatabase();
try {
final Cursor cursor = database.rawQuery("SELECT " + EventTable.KEY_NAME + ", " + EventTable.KEY_DATE_AS_STRING + " FROM " + EventTable.TABLE_NAME, null);
cursor.moveToFirst();
// ... rest of the logic, that iterates over the cursor, creates Event objects from the cursor columns and add them to the result list
return result;
} finally {
database.close();
}
}
So in that DAO, I have my databaseHelper object, which instanciates my class that extends SQLiteOpenHelper with the methods I talked about above.
And of course, I have interfaces to all my DAO, so that I can inject a Stub or mocked implementation in my tests (or experiment with different implementations if I want to try another solution based on SharedPreference for example)
And the code for my PlayerTable table:
public static void onCreate(SQLiteDatabase database) {
database.execSQL(TABLE_CREATE); // TABLE_CREATE is my "CREATE TABLE..." query
}
public static void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) {
// A bit blunt, that destroys the data unfortunately, I'll think about doing something more clever later ;)
database.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(database);
}

Categories

Resources