I'm using the SQLiteOpenHelper (shown below) in all my apps quiet happily. Lots of upgrades to existing databases ended successful in many year.
This time I need to upgrade the database in one of my apps and this step will last for some time. So I need to put these upgrade statements in their own thread.
What's the best place to do so?
Any help is highly appreciated.
public class MySQLiteOpenHelper extends SQLiteOpenHelper {
protected static final Object lock = new Object();
private static final int DATABASE_NAME = "mydatabase.db";
private static final int DATABASE_VERSION = 3;
private Context context;
private SQLiteDatabase database;
public MySQLiteOpenHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
this.context = context;
}
#Override
public void onCreate(SQLiteDatabase database) {
synchronized (lock) {
this.database = database;
// Create database statements
}
}
#Override
public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) {
synchronized (lock) {
this.database = database;
switch (newVersion) {
case DATABASE_VERSION:
switch (oldVersion) {
case 1:
upgradeFrom1To2();
case 2:
upgradeFrom2To3();
}
break;
}
}
}
private void upgradeFrom1To2() {
// Upgrade database statements
}
private void upgradeFrom2To3() {
// Upgrade database statements
}
}
I'd put the code in an IntentService, which is an easy way to implement a background thread for a long-running operation, especially one that is saving data. Use broadcast Intent to send status from the IntentService to other components, and BroadcastReceiver to receive these Intent, if you need to.
I would really avoid doing this in AsyncTask within an Activity; there's too much risk that the operation would be killed. Anyway, AsyncTask is much more complicated than IntentService.
One note: IntentService doesn't persist anything, including its class fields. To protect yourself, you may want to store state in SharedPreferences.
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!
First let you know I am new in Android.
Is it good practice to use ContentProvider to handle database table operations only for one application?
Trying to create multiple classes to handle database table operations. Created a database helper as follow:
public class WSDatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "wsemp";
private static final int DATABASE_VERSION = 5;
public WSDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
#Override
public void onCreate(SQLiteDatabase database) {
ItemTable.onCreate(database);
CustomerTable.onCreate(database);
}
#Override
public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) {
ItemTable.onUpgrade(database, oldVersion, newVersion);
CustomerTable.onUpgrade(database, oldVersion, newVersion);
}
}
Created a class to handle database table operation:
public class CustomerBean {
private WSDatabaseHelper database;
#Override
public boolean onCreate() {
database = new WSDatabaseHelper(getContext());
return false;
}
public boolean insertObject(valObj) {
SQLiteDatabase db = database.getWritableDatabase();
db.insert(CustomerTable.TABLE_CUST_ACCOUNT_INDEX, null, values);
}
}
But now I am not sure how I can call this insertObject function from my activity or session file. I tried by CustomerBean.isnertObject(obj) but it's asking to change the method to static.
Is it good practice to use ContentProvider to handle database table operations only for one application?
If your data is exclusive only for your application and other application cannot use it I don't see any reason to use ContentProviders. ContentProvider is used as an interface for sharing your application's data to other application. If your data can be shared or other application is dependent on it then you have to use ContentProvider.
Also you can create set of permissions to your content providers to restrict some operations in the provider.
On my app I make use of two datatabases.
This is the class that handles the database management and all the query that are made to it.
public class Database {
private DbHelper DBHelper;
private final Context Context;
private SQLiteDatabase MyDBone, MyDBtwo;
static Context ctx;
private static class DbHelper extends SQLiteOpenHelper {
public DbHelper(Context context, String dbName, int dbVersion) {
super(context, dbName, null, dbVersion);
}
#Override
public void onCreate(SQLiteDatabase db) {
// This is where the two databases are created
}
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVesion) {
// database upgrades are handled here
}
}
}
// database constructor
public Database(Context c) {
Context = c;
ctx = c;
}
// database open
public Database open() throws SQLException {
DBHelper = new DbHelper(Context, BD_NAME, BD_VERSION);
// I have here some if code to decide witch one of the bellow is used
if{
MyDBone = DBHelper.getWritableDatabase();
} else{
MyDBtwo = DBHelper.getWritableDatabase();
}
return this;
}
// database close
public void close() {
DBHelper.close();
}
public Cursor getData(........) {
// My querys are made here
}
}
My problem is that the databases are too big. In the onCreate method I'm getting the error: The code of method onCreate(SQLiteDatabase) is exceeding the 65535bytes limit. On the other side, my app is getting very big on size.
I would like to know what's the best way to address this issue since I can't change my databases.
Since my app must be run offline I can't make query's on a webserver.
I beleive that the best aproach would be to, on the first run of the app, download the databases from somewhere on the internet (drive, dropbox or other side) but since my programming skils are a little green I must pospone this to a must do in the future.
Is it possible, maintaining my Database class, prepack the apk with the databases and install them on the sdcard? On the other side this will increase the apk size (the total of the databases is 15 mb).
Please advise on the best way to address this issue.
Regards,
favolas
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 wrote some code to ensure that my database will be updated properly when I will release updates to my application.
The problem is that the OnUpdate() function of the SQLiteOpenHelper is never called.
Here is the code I wrote in the main activity -
SharedPreferences DB_ver = getSharedPreferences(PREFS_NAME, 0);
myDbHelper = new DataBaseHelper(con, DB_ver.getInt("DB_ver", 1));
try {
if(DB_ver.getInt("DB_ver", 1) !=getPackageManager().getPackageInfo(getPackageName(), 0).versionCode )
{
SharedPreferences.Editor editor = DB_ver.edit();
editor.putInt("DB_ver", getPackageManager().getPackageInfo(getPackageName(), 0).versionCode);
}
} catch (NameNotFoundException e) {
e.printStackTrace();
}
Here is the constructor of SQLiteOpenHelper(which extends SQLiteOpenHelper) -
public DataBaseHelper(Context context,int ver_code) {
super(context, DB_NAME, null, ver_code);
this.myContext = context;
}
Now I understood that the Super line is supposed to call the onUpgrade() function automatically, but it doesn't.
I've tested the function onUpgrade() separately, and it works.
Does anyone know what's the problem?
Thanks!
What your doing is really not neccessary. SQLiteOpenHelper does everything you need. Here's a possible scenario. SQLiteOpenHelper has a getVersion() method in case you need to query it at one point (I never did):
public class MySQLiteOpenHelper extends SQLiteOpenHelper {
private static final String dbname = "whatever";
private static final int dbversion = 1; // your first version
//private static final int dbversion = 2; // your second version
//private static final int dbversion = 3; // your third version
public MySQLiteOpenHelper(Context context) {
super(context, dbname, null, dbversion);
this.context = context;
}
#Override
public void onCreate(SQLiteDatabase sqliteDatabase) {
// ... Create first database content
}
#Override
public void onUpgrade(SQLiteDatabase sqliteDatabase, int oldVersion, int newVersion) {
switch (newVersion) {
case dbversion: // suppose your on third version
if (oldVersion == 1) {
upgradeFrom1To2(sqliteDatabase);
upgradeFrom2To3(sqliteDatabase);
}
if (oldVersion == 2) {
upgradeFrom2To3(sqliteDatabase);
}
break;
default:
break;
}
}
public void upgradeFrom1To2(SQLiteDatabase sqliteDatabase) {
// ...
}
public void upgradeFrom2To3(SQLiteDatabase sqliteDatabase) {
// ...
}
}
Two things:
You're not calling editor.commit().
You're creating the database with an initial version value of 1 in that code. Unless you're changing the version number in the AndroidManifest.xml it will never be anything but 1. Until that version changes onUpgrade() doesn't need to be called. onCreate() will be called when the database is first created, but onUpgrade() is only called if the reported version becomes different.
You should change the integer "VERSION" to get your onUpgrade called.
Also, the onUpgrade receive two integers, the first one, is the current version of the database(upgrading from), the second is the version you are upgrading to.
One thing I see is that you're not commiting your changes to the SharedPreferences that you're opening. You need to call editor.commit(); to save changes to SharedPreferences.
Also, have you tried actually opening the database in either read or write mode?