I have the following code below.
I am creating a database in my application that uses SQLiteOpenHelper.
I have couple of concerns and would appreciate some consults.
Direct answers for these were not found on stack overflow as they might be subjective.
1 - I will be using this database from several activities. However I am not planning on making this a singleton to avoid leaks, but rather I will be getting the getWritableDatabase() and getReadableDatabase() inside each method. I plan on doing a db.close() inside each activity's onDestroy() .Is this advisable ? given my app has couple of activites and is not a huge app.
2 - I am not following and DAO model, nor I am using a different class for every table.
The way I see it, I don't need to. Do I ?
3 - (A question rather than consult)
In the code below, I am not creating a database of the form
private SQLiteDatabase database;
So all the references to the database (from my activities) are being done via the methods in the same subclassed SQLiteOpenHelper, therefore I am referencing the physically created database directly via getWritableDatabase and getReadableDatabase.
Do I need to create an instance of SQLiteDatabase and use it ? Even inside the subclass of SQLiteOpenHelper ?
Below is the code.
public class DbHelper extends SQLiteOpenHelper
{
private static final String DATABASE_NAME = "myDbName";
private static final String DATABASE_TABLE = "myTable";
private static final int DATABASE_VERSION = 1;
private Context ctx;
public DbHelper(Context context)
{
super(context, DATABASE_NAME, null, DATABASE_VERSION);
this.ctx = context;
}
#Override
public void onCreate(SQLiteDatabase db)
{
db.execSQL("CREATE TABLE myTable(_id INTEGER PRIMARY KEY, title TEXT);");
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
//nothing here now. maybe later.
}
public void insertTitle(String title)
{
ContentValues titleCV = new ContentValues();
titleCV .put("title", title);
getWritableDatabase().insert(DATABASE_TABLE, null, titleCV );
}
public void getTitles()
{
Cursor result = getReadableDatabase().rawQuery("SELECT _id, title FROM myTable", null);
while (result.moveToNext())
{
int id = result.getInt(0);
String titleGotten= result.getString(1);
}
result.close();
}
Q1
If you have a scenario within your app that have two parallel threads accessing the database, use a single instance of the SQLiteOpenHelper (singleton or member in the Application or whatever). If not you don't need to.
about calling db.close(), if it is in the onDestroy(), then it's fine.
Q2
a DAO is an abstraction layer to ease maintaining and scaling your project. If you are not going to scale or maintain your code (upcoming releases or something), then I suppose you don't need one.
Q3
You don't need to create an instance of SQLiteDatabse. when you call getReadableDatabase() or getWritableDatabase(), SQLiteOpenHelper creates and maintains an instance. The same instance is used the next time you call getReadable\WritableDatabase().
let me know if you still have questions.
step 1: make a staic instace of SqliteOpenHelper
step 2: you never close conexion to database, sqlite manage itself the sequencial access to write or read :)
private static ControladorBBDD instancia;
my class: public class ControladorBBDD extends SQLiteOpenHelper {
default :
private ControladorBBDD(Context ctx_p) throws Exception {
super(ctx_p, DB_NAME, null, DATABASE_VERSION);
try {
ControladorBBDD.ctx = ctx_p;
DB_PATH = ctx.getDatabasePath(DB_NAME).getAbsolutePath();
String myPath = DB_PATH;// + DB_NAME;
this.createDataBase();
db = SQLiteDatabase.openDatabase(myPath, null,
SQLiteDatabase.OPEN_READWRITE);
} catch (SQLiteException ex) {
Conexiones.escribirLog(Log.getStackTraceString(ex),
ctx.getString(R.string.versionReal));
db.close();
}
}
and my way to implement a conexion to database:
public static synchronized ControladorBBDD getBBDD(Context ctx_p)
throws Exception {
if (instancia == null) {
instancia = new ControladorBBDD(ctx_p);
}
return instancia;
}
and to call it from activities:
dblectura = ControladorBBDD.getBBDD(getApplicationContext());
where private ControladorBBDD dblectura;
i hope that it helps, important thing is that you use applicationContext, no Activity context ;))
well if i were u i would create a class and the dbhelper as a subclass then i would use a open and a close function for main class and also the insert
whenever i want to use database i do it like this
mainclass mc=new mainclass(this);
mc.open();
mc.insert();
mc.close();
Related
As a new Android programmer, I followed various online examples to create my own class that extends SQLiteOpenHelper...
public class MySQLiteHelper extends SQLiteOpenHelper {
private static final int DATABASE_VERSION=1;
private static final String DATABASE_NAME="MyDB";
// Main Database Operations
public MySQLiteHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_A);
db.execSQL(CREATE_TABLE_B);
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
...
public long addRecord(){
SQLiteDatabase db = this.getWritableDatabase();
....
etc
This works throughout my various activities with no issues. When I want to use it, I call...
MySQLiteHelper db = new MySQLiteHelper(this);
Then I call the various routines like...
db.addRecord();
Now I have created a secondary class that I need to use throughout the application. In that class, there are some routines that need to process data from the database. Problem is, I can't declare the MySQLiteHelper because of the this which errors out.
Doing some online search I think I understand that I need to get the Context from the application (or the calling activity??), but not sure how to go about doing that with this code.
As I mentioned, I am new to Android... so any code examples would be greatly appreciated.
EDIT
I need to clarify this secondary class as mentioned above. From the aforementioned online examples, the second class is used to "hold" the database record information. It looks something like this...
public class Acct {
private int _id;
private String _name;
private String _phone;
public Acct() {
}
public int get_id(){
return this._id;
}
public void set_id(int id) {
this._id=id;
}
public void set_name(String name){
this._name=name;
}
public String get_name(){
return this._name;
}
public void set_phone(String phone){
this._name=phone;
}
public String get_phone(){
return this._phone;
}
...
This is typically used with something like this in an activity...
MySQLiteHelper db = new MySQLiteHelper(this);
Acct acct = new Acct();
acct=db.getAccount(searchId);
myEditText.setText(acct.get_name());
...
Where my problem arises, I want to create a routine and code it IN the Acct class so it can be referenced such as...
acct.UpdateData();
This UpdateData routine is where we need to access the db and thus the MySQLiteHelper. It needs to be able, for each account, to go into the database, access some information from another table, do some processing, and store a summary back into this table for easier reference. As mentioned, there is no Context in the Acct class, so this is where I am getting confused.
And to make matters worse, because the Acct class is a 'holding' place for data from the DB, the online examples also use Acct IN the 'MySQLiteHelper' itself during the getAccount routine....
public Acct getAccount(int id){
SQLiteDatabase db = this.getReadableDatabase();
String SQL_STRING="SELECT * FROM "+ACCT_TABLE+" WHERE "+ACCT_FLD_ID+" = "+String.valueOf(id);
Cursor cursor =
db.rawQuery(SQL_STRING, null);
Acct acct = new Acct();
if (cursor!=null) {
cursor.moveToFirst();
acct.set_id(cursor.getInt((cursor.getColumnIndex(ACCT_FLD_ID))));
acct.set_name(cursor.getString(cursor.getColumnIndex(ACCT_FLD_NAME)));
acct.set_phone(cursor.getString(cursor.getColumnIndex(ACCT_FLD_PHONE)));
cursor.close();
} else {
acct = null;
}
db.close();
return acct;
}
I hope all this additional helped clarify what I am trying to do for the couple comments and answers posted so far. If you need more information, please ask. I'd like to get this to work, just still not sure how.
Your problem is you need a Context to call the constructor of MySQLiteHelper. You've been successful doing so in an Activity (which is a Context), but now you have some other class (which I will call "Foo") that isn't a Context and doesn't have one.
A quick solution is to make Foo take a Context in its constructor, and instantiate your MySQLiteHelper like so:
public class Foo {
private MyOpenHelper openHelper;
public Foo(Context context) {
openHelper = new MyOpenHelper(context.getApplicationContext());
}
}
If Foo is a singleton, you can do the same thing in whatever method obtains the instance (i.e. force the caller to provide a Context). Every application component either is a Context (Activity, Service) or has a Context (BroadcastReceiver gets one in onReceive(), ContentProvider has getContext()).
The use of getApplicationContext() here is worth noting: The Application object for your app is always a singleton--only one instance of it will exist for as long as your app is running (this is guaranteed by the OS). Activities can be destroyed, and creating a MySQLiteHelper with one can cause a memory leak. The application Context always exists and so it cannot be leaked.
Instead of using this, Try to use MainActivity.this or getApplicationContext().
Hope this help!
I'm getting this error when I run my app for the first time after re-install:
android.database.sqlite.SQLiteException: no such table
(This error happens when my app tries to read from the database)
For some reason the onCreate() method in DBHelper is not getting called and therefore the tables are not getting created. I followed the advice from other question and tried calling getWritableDatabase(), also tried a create() call to insert data in some table, but still no luck: onCreate is never called.
I got it to work however by changing the DATABASE_VERSION value to 2. But that doesn't make sense since this is a brand new installation after uninstall.
Also I found that before the SQL read error the database got created but it has only 1 table "android_metadata" (not created by me).
I'm posting some code here for reference
public class DatabaseHelper extends OrmLiteSqliteOpenHelper{
private static final String DATABASE_NAME = "RoutePlanner.db";
private static final int DATABASE_VERSION = 1;
private Dao<Trip, Integer> tripDAO = null;
private RuntimeExceptionDao<Trip, Integer> tripRunTimeDAO = null;
...
}
#Override
public SQLiteDatabase getWritableDatabase() {
return super.getWritableDatabase();
}
public DatabaseHelper(Context context){
super(context, DATABASE_NAME,null, DATABASE_VERSION, R.raw.ormlite_config);
}
#Override
public void onCreate(SQLiteDatabase db, ConnectionSource source) {
try {
Log.i(DatabaseHelper.class.getSimpleName(), "onCreate");
TableUtils.createTable(source, Trip.class);
...
} catch (SQLException ex) {
Log.e(DatabaseHelper.class.getSimpleName(), "Error creating db", ex);
throw new RuntimeException(ex);
}
}
OK, I found the problem, hope this explanations helps others on what NOT to do. The issue was that I had a separate calendar module which I wanted to access my Database. To make things 'simpler' I created a separate DatabaseHelper on that module to access the same SQLite databse as my main module. The existance of the 2nd DatabaseHelper was causing all my issues. Solutions are either join the 2 modules into one, or use a Database Service Provider
Exception:
CREATE TABLE android_metadata failed
Failed to setLocale() when constructing, closing the database
android.database.sqlite.SQLiteException: database is locked
My app works fine and has no db issues, except when onUpgrade() is called.
When onUpgrade is automatically called, it tries to use the CarManager class below to do data manipulation required for the upgrade. This fails because the db is locked.
Because this seems like it should be a normal thing to do, it seems that I must not be structuring the following code correctly (two classes follow, a helper and a table manager):
public class DbHelper extends SQLiteOpenHelper {
private Context context;
//Required constructor
public DbAdapter(Context context)
{
super(context, "my_db_name", null, NEWER_DB_VERSION);
this.context = context;
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
overrideDB = db;
CarManager.migrateDataForOnUpgrade(context);
}
}
public class CarManager {
DbHelper dbHelper;
public CarManager(Context context)
{
dbHelper = new DbHelper(context);
}
public void addCar(String make, String model)
{
ContentValues contentValues = new ContentValues();
contentValues.put("make", make);
contentValues.put("model", model);
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.insert("car", null, contentValues);
db.close();
}
public static void migrateDataForOnUpgrade()
{
//Code here that migrates data when onUpgrade() is called
//Db lock happens here
}
}
Any ideas?
Do people set up table manager (ex: dao) differently than this?
edit: I talked to the google team # android developer hours, and they said onUpgrade3 was never meant to do anything like structural changes (alters). So yes, it seems like there are some hacks that must be used in many instances right now.
I use the following model by extending the Application class. I maintain a single static instance of my db helper which all other app components use...
public class MyApp extends Application {
protected static MyAppHelper appHelper = null;
protected static MyDbHelper dbHelper = null;
#Override
protected void onCreate() {
super.onCreate();
...
appHelper = new MyAppHelper(this);
dbHelper = MyAppHelper.createDbHelper();
dbHelper.getReadableDatabase(); // Trigger creation or upgrading of the database
...
}
}
From then on any class which needs to use the db helper simply does the following...
if (MyApp.dbHelper == null)
MyApp.appHelper.createDbHelper(...);
// Code here to use MyApp.dbHelper
I'm trying to get a better understanding of the SQLiteOpenHelper class and how and when onCreate and onUpgrade are called.
My problem is that each time I quit and start my application (technically it's actually each time I create a new instance of MyDB), onCreate is called, and all of the data from the previous usage is effectively wiped... WTF???
I got around this problem initially by creating a singleton MyDBFactory where I created a single instance of MyDB. This allowed the data to be persistent while the application is running at least.
What I would like is for my database schema and data to be persistent!
I basically have:
public class MyDB extends SQLiteOpenHelper{
private static int VERSION = 1;
...
public ContactControlDB(Context context) {
super(context, null, null, VERSION);
}
#Override
public void onCreate(SQLiteDatabase db) {
try {
db.execSQL(DATABASE_CREATE);
db.execSQL(INSERT_DATA);
} catch (SQLException ex) {
ex.printStackTrace();
}
}
and:
public class MyDBFactory{
private static MyDB db;
public static MyDB getInstance(Context context) {
if(db == null) {
db = new MyDB (context);
}
return db;
}
}
What I'd like to know is why onCreate is called every time I have 'new MyDB(context)', and where my database goes each time my app exits.
If you have some appropriate links, or some knowledge that would clue me up a bit, I'd greatly appreciate it!
the line:
super(context, null, null, VERSION);
the second parameter is null specifying that the database should be created in memory, you should give it a name.
Android reference
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