I'm developing an app and using SQLite, contentResolver and contentProvider.
Application description
My app searches for contacts in internal SQLite DB. the data arrives from external file that is selected on first lunch or when the user press the update option in the menu.
All access to the DB are done using getContentResolver().
ContentProvider
I have a ContactsContentProvider class that extends ContentProvider and holds a reference to ContactsDBAdapter which is my database adapter that extends SQLiteOpenHelper.
(I hope you are all with me until now ).
Problem description
When a user press the update button I want the DB to drop all tables and load the new data (this is done by my file chooser and works great).
in order for the onUpgrade() in my ContactsDBAdapter to work the content provider onCreate() must be called with a higher version then what it had before
#Override
public boolean onCreate() {
context = getContext();
pref = PreferenceManager.getDefaultSharedPreferences(context);
int dbVersion = pref.getInt(Settings.DB_VERSION, 1);
mDb = new ContactsDBAdapter(context,dbVersion);
return (mDb == null)? false : true;
}
But I get the contentProvider from my contentResolver so it is not created twice.
Although there are a lot of explanation of how to use both contentProvider and contentResolver I didn't find anywhere a good upgrade progress.
I'm aware of how the onUpgrade works and that it is being checked during getReadableDatabase() and getWritableDatabase() calls but the fact is that the version will not be diferent since the ContactsDBAdapter is the same instance as it previously was.
I thought about some work arounds but didn't like them at all.
I can manually check during insert() if the version is higher (but that would be expensive since it is done by every call) and if the answer is true then manually call onUpgrade.
or to try and unregister the Provider in some way but didn't find any valid and good solution so far.
What is the best practice to upgrade your DB ?
Thanks!
I would suggest you update the shared preferences to change the db version and add it:
SharedPreferences.Editor ed = pref.edit();
ed.putInt(Settings.DB_VERSION, dbVersion + 1);
ed.commit();
Hopefully it helps you
I found a nice solution.
In my ContactsContentProvider which extends ContentProvider I added a sharedPrefencesListener
#Override
public boolean onCreate() {
context = getContext();
pref = PreferenceManager.getDefaultSharedPreferences(context);
int dbVersion = pref.getInt(Settings.DB_VERSION, 1);
mDb = new ContactsDBAdapter(context,dbVersion);
pref.registerOnSharedPreferenceChangeListener(sharedPrefListener) ;
return (mDb == null)? false : true;
}
SharedPreferences.OnSharedPreferenceChangeListener sharedPrefListener =
new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
if(key.equals(Settings.DB_VERSION)){
int dbVersion = sharedPreferences.getInt(Settings.DB_VERSION, 1);
synchronized (mDb) {
mDb.close();//Not sure this is the right behavior
mDb = new ContactsDBAdapter(context,dbVersion);
}
}
}
};
Now when the user changes the version number in my main activity then I set the version number. This will call a new ContactsDBAdapter which will then invoke onUpgrade the next time someone will want getReadableDatabase() or getWriteableDatabase().
Related
My app uses an SQLite DB, wrapped with a SQLiteOpenHelper and a ContentProvider. I added a sign-in feature to the app, and now I want every user to only be able to see his own data. The way I thought to achieve this is for the app to create a separate DB for every user that signs in to the app, and use the user's ID in the filename of the database.
I have this ContentProvider:
public class MyProvider extends ContentProvider {
//...
#Override
public boolean onCreate() {
dbHelper = new MyDBHelper(getContext());
return true;
}
I have this SQLiteOpenHelper:
public class MyDBHelper extends SQLiteOpenHelper {
Which has this constructor:
public MyDBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
Up until now the app couldn't have multiple users, so it just had one database. so DB_NAME was always the same String. I now tried to set it like that:
private static String UID = FirebaseAuth.getInstance().getCurrentUser().getUid();
public static final String DB_NAME = String.format("data%s.db", UID);
(As you can see, I'm using Firebase Authentication)
but this resulted in a crash, because apparently the content provider is created on app start, before the user has authenticated. (so user is null. Yeah, I should check that user is not null before I try to call getUid(). but this won't make this thing work)
So this doesn't seem like the right approach. How can I use a different DB according to the signed user? Can I make the content provider to first be created after a user has authenticated?
I could also just keep everything in one database and add a UID column. But will this be protect the different users' data good enough from each other? Also, this would mean a lot more code changes.
How can I use a different DB according to the signed user?
The simple solution is to get rid of the ContentProvider. The only reason to use a ContentProvider is if you are going to be serving this data to other apps.
Also, I would be wary of just taking getUid() and putting it in a filename. You are not in control over what getUid() returns, and it might someday contain characters that are invalid in filenames.
Can I make the content provider to first be created after a user has authenticated?
No, sorry.
Seems that the right solution here is to not use ContentProviders. So I accepted the other answer.
But to answer my actual question, for people that are determined to make different DBs work with one ContentProvider, here is how it can be done:
I changed the custom SQLiteOpenDBHelper's constructor to also take a uid:
public MyDBHelper(Context context, String uid) {
super(context, String.format(DB_NAME, uid), null, DB_VERSION);
UID = uid;
}
and I changed the onCreate of my ContentProvider not to create the DBHelper. I created this function that initializes the DBHelper instead:
public void initDB(Uri uri) {
String uid = uri.getPathSegments().get(0);
if (dbHelper == null){
dbHelper = new MyDBHelper(getContext(), uid);
} else if (!uid.equals(dbHelper.UID)){
dbHelper.close();
dbHelper = new MyDBHelper(getContext(), uid);
}
}
and I call this method at the start of the query, insert, update and delete methods.
So the DBHelper which holds the open connection to the DB, is initialized whenever the content provider is preforming some action on the DB but there is either not yet an existing connection with the DB, or the connection is with a DB of a different user.
This is not the right way to solve this problem and this probably has consequences in some cases. But I didn't want to leave the question I asked unanswered.
I don't know how to handle this correctly without getting database locked errors.
My app basically downloads many items in batches of 100 rows (~ 60.000 items) and inserts them in the database. Each batch of 100 rows is processed into a transaction.
The main activity allows the user to navigate between screens (fragments) while records are being downloaded and inserted. Most of the other screens contains read data from the database. I get a lot of database lock errors during reading. All readings are done in the main activity (not fragments) in different async tasks
So far I just used the "classic approach"
public class DBAdapter {
public DBAdapter(Context ctx) {
this.context = ctx;
DBHelper = new DatabaseHelper(context);
}
private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DB_CREATE_TABLES);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Utils.log("Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data");
onCreate(db);
}
public DBAdapter open() throws SQLException {
database = DBHelper.getWritableDatabase();
return this;
}
public void close() {
DBHelper.close();
}
Then on my activity's onCreate() I call db = new DBAdapter(context); and each time I am doing an database operation (read/write) I call db.open() and after the insert/read is done I call db.close()
My questions are:
what would be the best approach to this situation ?
Considering I do a lot of write/read would it be better to call db.open on onCreate and db.close() on onDestroy() ? Would this be better than calling open/close each time I need to access the database ?
What do I need to do to avoid database locking on reading ?
I had a exactly similar situation like yours. In addition to what you described, in my app the user also can update the database through input on the screen.
The way I resolved it ( I don't know if it's the best way, but I hardly see any locking issue now)
Make a singleton class derived from SQLiteOpenHelper to make sure only one instance is running at any given time.
Implement ContentProvider class for insert/update/delete/query operations. Make all those functions 'synchronized'
Only close the db in ContentProvider's shutdown function. I do a very frequent db operations, so I don't want to open/close everytime. But I am not sure if it's the correct way of handling it.
Do access DB only through ContentProvider interface from anywhere
A very simple approach, or maybe a workaround is using synchronized methods for opening and closing the database object. I don't really know if it's the best practice, but at least it's simple and easy. Add this methods to your DBAdapter Class, and use them instead of db.open and db.close. The use_count attribute simple holds how many times open has been called. Initialize it with a value of 0. Also, in order to make it work on your solution be sure to pass the same DBAdapter object between the fragments. Don't create a new one everytime :
private int use_count = 0;
public synchronized void doOpen()
{
use_count++;
this.open();
}
public synchronized void doClose()
{
use_count--;
if (use_count == 0)
{
this.close();
}
}
Consider wrapping the SQLite database in a ContentProvider and using CursorLoader to do the queries from the various activities & fragments. This isolates the management of the database from the Activity/Fragment life cycle and can result in many fewer open/close cycles.
You may still run into contention between the reads and writes, but having all the database interaction in the same module should make it easier for you to address these issues.
Some interesting links: http://www.vogella.com/articles/AndroidSQLite/article.html#todo
When to use a Content Provider
I have created a databaseprovider class which uses single instance of db object. Object is created in main activity and closed onDestroy method. This seems ok (but get some errors such as: db already closed or db is not open on some users devices that I cannot simulate).
I want to add a service to the application for the content download and this service can run with scheduler which make me think about single instance of db object will not work. Should I use another object for the service, will it result consistency problems? Can you kindly advice what would be the best way?
Databaseprovider class exm:
public class DatabaseProvider {
private static DatabaseHelper helperWriter;
public static SQLiteDatabase db_global;
public DatabaseProvider(Context c) {
helperWriter = DatabaseHelper.getHelper(c, true);
}
private static SQLiteDatabase getDB() {
if(db_global == null)
db_global = helperWriter.getWritableDatabase();
else if(!db_global.isOpen()) {
try {
db_global.close();
}
catch(Exception ex) {
ex.printStackTrace();
}
db_global = helperWriter.getWritableDatabase();
}
return db_global;
}
public String GetVersion() {
SQLiteDatabase db = getDB();
Cursor c = db.query(DatabaseHelper.PARAMETER_TABLE_NAME, new String[] {"VALUE"}, "KEY='Version'", null, null,null,null);
String version = "";
if(c.moveToNext())
{
version = c.getString(0);
}
else
version = "0";
c.close();
return version;
}
public long UpdateVersion(String value) {
ContentValues initialValues = new ContentValues();
initialValues.put(DatabaseHelper.PARAMETER_COLUMN_VALUE, value);
SQLiteDatabase db = getDB();
long r = db.update(DatabaseHelper.PARAMETER_TABLE_NAME, initialValues, "KEY='Version'", null);
if(r <= 0)
r = helperWriter.AddParameter(db, "Version", value);
//db.close();
return r;
}
public void CloseDB() {
if (db_global != null)
db_global.close();
db_global = null;
helperWriter.close();
}
}
Not sure if this will help, but...
you can't rely on onDestroy() in case the app crashes. Android may also keep your app in RAM, even if you exit it. Also, your main activity may get destroyed while the app is getting used if you are on a subactivity. It can also get recreated.
Sometimes it's better to have calls that open the DB, does stuff to it, and then closes it within the same function. If you are using a service, it may actually help things. I also am not sure if you should have a situation where a DB can be opened and/or accessed from a variety to different places at once without some management code
I see a couple questions:
A)
(but get some errors such as: db already closed or db is not open on some users devices that I cannot simulate).
...
Start an activity, then update content and some db operations in AsyncTask. While update is in progress go back and start the same activity again.
To work around these errors have you considered using a [Loader][1]? It's a callback based framework around ContentProviders.
B)
add a service to the application for the content download and this service can run with scheduler which make me think about single instance of db object will not work. Should I use another object for the service, will it result consistency problems?
This post by #commonsware from this website, suggests not to use Service for long running tasks. Instead the AlarmManager is suggested. I've only worked with short running services (for audio IO) myself.
I use a ContentProvider in my app and everything works great except for one little issue. I have a backup and restore function that backs up the database to a file on the SD card and then those backup files can be restored to overwrite the current database. This whole process is working, but the ContentProvider still holds the reference/cache to the original database once one of the old backup files is restored. I can't seem to find a way to refresh or reload the database reference in the ContentProvider. I know the restore works because I can see the records in the db with SQLite Editor and when I close and re-open the app, it displays the correct records.
Does anybody know a way to do this? Is there a way to close and re-open the ContentProvider that I'm not seeing?
If you are targeting >= API 5 you can get a reference to your ContentProvider via a ContentProviderClient, and run a method specific to your implementation:
ContentResolver resolver = context.getContentResolver();
ContentProviderClient client = resolver.acquireContentProviderClient("myAuthority");
MyContentProvider provider = (MyContentProvider) client.getLocalContentProvider();
provider.resetDatabase();
client.release();
Add the reset method to your ContentProvider implementation:
public void resetDatabase() {
mDatabaseHelper.close();
mDatabaseHelper = new MyDatabaseOpenHelper(context);
}
Are you maintaining a reference to the actual SQLiteDatabase in your content provider (something like calling SQLiteOpenHelper.getWritableDatabase() in onCreate() and then keeping that reference)? Or do you get the DB object from someplace like a helper in each provider method?
Typically, if you only keep a local reference to the helper and get the writable/readable database instance inside of each method as needed then this problem should go away. If not, perhaps we can take a look at the provider code?
Hope that Helps!
Here is my solution.
public class DataProvider extends ContentProvider {
private DataDbHelper dbHelper;
#Override
public boolean onCreate() {
// nothing here
return true;
}
private DataDbHelper getDbHelper() {
if (dbHelper== null) {
// initialize
dbHelper = new DataDbHelper(getContext());
} else if (dbHelper.getReadableDatabase().getVersion() != DataDbHelper.VERSION) {
// reset
dbHelper.close();
dbHelper = new DataDbHelper(getContext());
}
return this.mOpenHelper;
}
}
query(), insert(), update(), delete() use getDbHelper() to obtain an SQLiteDatabase
The full code of my Android app is available here if you need more info.
You can also simply use the delete method without a selection:
context.getContentResolver().delete(YourProvider.CONTENT_URI, null, null);
Hi I am new to android and I have a problem in creating a database.
public class database extends ListActivity {
/** Called when the activity is first created. */
private final String MY_DATABASE_NAME = "myCoolUserDB.db";
private final String MY_DATABASE_TABLE = "t_Users";
Context c;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ArrayList<String> results = new ArrayList<String>();
setContentView(R.layout.main);
SQLiteDatabase mydb=null;
try
{
mydb.openOrCreateDatabase(MY_DATABASE_NAME, null);
} catch(Exception e){}
}
}
When I run this code it throws a run time exception. Please help me.
If you are going to call a static method like openOrCreateDatabase, do it on the class (SQLiteDatabase.openOrCreateDatabase(...)), not an instance. It's a lot clearer - the way you've done it looks like you're calling an instance method, so looks like a sure NullPointerException, which of course is misleading.
As someone else has stated, the stack trace would be the most useful thing when asking for help with an exception.
(Almost) never catch an exception without at the very least logging it. Don't just do nothing with it. There are of course exceptions to every rule, but let's not go there for the moment. Anyway, if you don't at least log it, you're just throwing away information that would tell you what went wrong when everything goes to crap later.
You shouldn't be using that method directly, and should instead be extending SQLiteOpenHelper . See the android developers page on data storage to get started (I'd post a link but apparently I'm only allowed one link in my post ?!), and since you've probably had to download the SDK to get going, look in the samples that come with it for the Notepad sample application. That contains a NotePadProvider class, which is a good example of both a content provider and database access, which often go hand-in-hand on android. I'd suggest compiling that application and making some simple changes to it before you jump into making your own one.
For working with sqlite database you need to create class extended from SQLiteOpenHelper:
private class DBHelper extends SQLiteOpenHelper {
public DBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLES);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(UPGRADE_TABLES);
}
}
Then you can get access to db using DbHelper object:
DBHelper dbHelper = new DBHelper(Activity.this);
SQLiteDatabase db = dbHelper.getReadableDatabase();
I run into the same problem. It figures out that two bugs happens during development
dir "databases" was not existent
accendently ".db" was created as directory.
They following code cover both
File dbFile = getDatabasePath ("abc.db");
if (dbFile.isDirectory ()) {
dbFile.delete();
}
if (! dbFile.exists()) {
String path = dbFile.getParent ();
new File (path).mkdirs ();
}
database = SQLiteDatabase.openDatabase (dbFile.getAbsolutePath (), this, SQLiteDatabase.OPEN_READWRITE | SQLiteDatabase.CREATE_IF_NECESSARY);
Hope this helps
I think SQLiteOpenHelper is only useful for "single table" databases. For multiple table applications I consider directly using SQLiteDatabase fit better to a good architecture.
This is a simple post which tells you how to insert data in to a SQLite database in Android and further more this links shows you how to retrieve data from a SQLite database in Android .