I am trying to make my SQLite database globally accessible throughout my Android application using a singleton pattern:
public class MySQLiteOpenHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "foo";
private static final int DATABASE_VERSION = 1;
private static MySQLiteOpenHelper instance;
private MySQLiteOpenHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public static MySQLiteOpenHelper getInstance(Context context) {
if (instance == null) {
instance = new MySQLiteOpenHelper(context);
}
return instance;
}
public static MySQLiteOpenHelper getInstance() throws UnsupportedOperationException {
if (instance == null) {
throw new UnsupportedOperationException();
}
return instance;
}
...
The idea here is that the first call is to getInstance(Context context) passing in the Application object. Thereafter I can just use getInstance() where I can't get a handle on a Context object.
However, I come unstuck when I make the initial MySQLiteOpenHelper.getInstance(getApplication()) call:
java.lang.IllegalStateException: getDatabase called recursively
at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:204)
at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:164)
Can anyone shed any light on what is happening here or suggest a better technique for achieving my goal?
Cheers!
The code you posted is just fine.
java.lang.IllegalStateException: getDatabase called recursively
This is caused when you call e.g. getWritableDatabase() recursively.
One common cause for it is to call getWritableDatabase() in database helper onCreate(). The onCreate() gets invoked when getWritableDatabase() is called and the database does not exist. Calling getWritableDatabase() again is an error. You should use the writable SQLiteDatabase passed to onCreate() as an argument instead.
Your MySQLiteOpenHelper should works fine.
That class is not the problem.
Related
I am currently working on an Android project that performs a lot of communication with the SQLite database. I am also trying to implement a MVP framework within the app.
My current implementation of the Singleton instance is similar to the following. (taken from this post: https://github.com/codepath/android_guides/wiki/Local-Databases-with-SQLiteOpenHelper )
public class PostsDatabaseHelper extends SQLiteOpenHelper {
private static PostsDatabaseHelper sInstance;
public static synchronized PostsDatabaseHelper getInstance(Context context) {
if (sInstance == null) {
sInstance = new PostsDatabaseHelper(context.getApplicationContext());
}
return sInstance;
}
private PostsDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
}
With the existing code above, I call the getInstance method in several Presenter classes, passing into each of them the Context object which was passed on from the Activity/Fragment. The Context objects can be passed across multiple classes.
Instead of the code above, I was thinking of instantiating the databaseHelper only once at the start of the Application, and then all references will then point to a variant of the getInstance method without the context dependency.
EDIT: My main aim here is to remove as much as possible, the presence of the Context object in the Presenter classes, so as to make the code 'cleaner'. Because all the calls to getInstance provide/inject the same type of Context (the Application's context and not an Activity-specific context), I don't see a need to put the Context object as an argument.
public class PostsDatabaseHelper extends SQLiteOpenHelper {
private static PostsDatabaseHelper sInstance;
// called by all other classes
public static synchronized PostsDatabaseHelper getInstance() {
if (sInstance == null) {
//throw error
}
return sInstance;
}
// only called once at the start of the Application
public static void instantiateInstance(Context context){
sInstance = new PostsDatabaseHelper(context.getApplicationContext());
}
private PostsDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
}
What I would like to know is, will there be any downsides to this approach? Thanks!
You are trading lazy initialization for static initialization.
In general, lazy initialization can amortize the cost of initialization over the life of an application. In this case it seems less important, for two reasons:
It is almost certain that you will need this DB. It seems unlikely that by putting the initialization off, you might avoid having to do it at all.
The Android framework guarantees that the DBHelper constructor can be run from the UI thread: it is not what takes the time. The thing that takes the time is the first call to getWriteableDatabase. The lazy creation of the Helper accomplishes almost nothing.
You might consider making the code even less convoluted, by initializing the DB in the Application like this:
public class DBDrivenApp extends Application implements DBProvider {
// ...
private PostsDatabaseHelper db;
// ...
#Override
public void onCreate() {
super.onCreate();
db = new PostsDatabaseHelper(this);
}
#Override
public PostsDatabaseHelper getDB() { return db; }
// ...
}
... and, better yet, use an IoC framework, like Dagger2, to inject the database instance, so that you can mock it in testing.
I want to Use a Singleton to instantiate the SQLiteOpenHelper like here.
I have tried put this code in my SQLiteOpenHelper class:
public static synchronized DatabaseHelper getInstance(Context context) {
if (sInstance == null) {
sInstance = new DatabaseHelper(context.getApplicationContext());
}
return sInstance;
}
But when I try to call that method from my repository class like this:
public UserAccountRepository(Context context) {
super(context);
dbHelper = new DatabaseHelper.getInstance(context);
}
android studio shows the error:
cannot resolve symbol getinstance
new is used to create a new object and to call it constructor.
This is not what you actually want to do (that's already done by getInstance); you just want to assign the return value of getInstace to some local variable.
Just drop that new.
I want to open the database once only from the main screen of my app , and I want to use this instance anywhere in any activity. Is that possible or should I make the context to be each actual opened activity so that I must create an instance of the database ( open ) in every activity ?
Is that possible or should I make the context to be each actual opened
activity so that I must create an instance of the database ( open ) in
every activity ?
it is possible, and you could use the application context. Your DBHelper could be a singleton. E.g
public class DBHelper extends SQLiteOpenHelper {
private static DBHelper sInstance;
public static synchronized DBHelper getInstance(Context context) {
if (sInstance == null) {
sInstance = new DBHelper(context.getApplicationContext());
}
return sInstance;
}
private DBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
}
You do not need to close and re-open the SQL connection per each individual Activity.
Having said that - it is best to open the connection using an app context, to avoid Activity leaks.
You can get an app context refrence quite easily.
HI i am using an application in that there is so many fragments all fragments are using database, I just want to know how to manage database open close in this situation
I have more than 10 fragments which are using database should i have to open close database for each fragment or should I have to open database once app start and then close at the end of application
if that possible then how please explain me
You can simply create a Singleton helper which you can use throughout the lifecycle of the application as noted here.
public class DatabaseHelper extends SQLiteOpenHelper {
private static DatabaseHelper sInstance;
private static final String DATABASE_NAME = "database_name";
private static final String DATABASE_TABLE = "table_name";
private static final int DATABASE_VERSION = 1;
public static DatabaseHelper getInstance(Context context) {
// Use the application context, which will ensure that you
// don't accidentally leak an Activity's context.
// See this article for more information: http://bit.ly/6LRzfx
if (sInstance == null) {
sInstance = new DatabaseHelper(context.getApplicationContext());
}
return sInstance;
}
/**
* Constructor should be private to prevent direct instantiation.
* make call to static factory method "getInstance()" instead.
*/
private DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
}
Yes both way you can do this as per your requirement if you want to open database throughout all fragment then open it in Parent Activity onCreate method and Close it to OnDestroy method.
Or
Open Database in onAttach method in fragment and close in onDetach method that's it...
I have a little concern about how I handle my database in my apps, here is basically what I do:
I have a custom class extending SQLiteOpenHelper that handles all the transactions with the DB.
In my app, I have one single Activity and several Fragments that are created, deleted, hidden or shown during the process.
In every Fragment where I need to modify or access data from the DB, I declare a static variable:
private static DatabaseHandler mDB;
And I initialize it this way on the onCreate() methods:
mDB = DatabaseHandler.getInstance(getActivity());
All this is working, my concern is about the variable itself, is it a good idea to declare it as a static variable in all my custom fragment classes?
I also use the same way a class containing the main parameters of the app using mParams = Parameters.getInstance(getActivity());, should I also declare it as static?
I want to avoid memory leaks and NPE but I am not sure what is the right way to handle that.
FYI, the beginning of my DatabaseHandler class:
public class DatabaseHandler extends SQLiteOpenHelper {
private static DatabaseHandler sInstance = null;
(...)
private Resources mResources;
public static DatabaseHandler getInstance(Context context) {
if (sInstance == null) {
sInstance = new DatabaseHandler(context);
}
return sInstance;
}
private DatabaseHandler(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mResources = context.getResources();
}
(...)
Thank you
I normally create a SQLiteDatabase static instance in my Application class and access across the app.
So , I have a custom class which returns SQLiteDatabase instance on application create.
This is my Application class
public class MainApplication extends Application {
private static SQLiteDatabase mSQLiteDatabase = null;
#Override
public void onCreate(){
super.onCreate();
SQLiteAsyncTask sqLiteAsyncTask = new SQLiteAsyncTask(getApplicationContext());
mSQLiteDatabase = (SQLiteDatabase) sqLiteAsyncTask.loadInBackground();
}
// method to get the sqlite db instance
public SQLiteDatabase getSQLiteInstance(){
return mSQLiteDatabase;
}
}
Now you can get the SQLiteDatabase instance from Activity or Fragment , by
MainApplication.getSQLiteInstance()