I am using SQLite on Android, ICS. When I make an update, I get a SQLiteReadOnlyDatabaseException. That was weird because i created it with SQLiteOpenHelper.getWritableDatabase(). Also, I tested if the database is readonly just before the update occurs with myDB.isReadonly(). This exception doesn't occur on Gingerbread. I suspect it has something to do with different versions of sqlite across android versions.
Here is my code for the creation of the database.
protected static class DBHelper extends SQLiteOpenHelper {
DBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CreateStatements.CREATE_MOVIES);
...
}
public class DBAdapter {
public static final String PACKAGE_NAME =
protected DBHelper dbHelper;
protected Context context;
protected SQLiteDatabase myDB;
public CreateStatements statement;
public DBAdapter(Context context) {
this.context = context;
}
public void open() throws SQLException {
dbHelper = new DBHelper(context);
myDB = dbHelper.getWritableDatabase();
myDB.setLockingEnabled(false);
}
public void close() {
myDB.close();
}
...
}
I recently faced. Here is how it can be solved:
try{
myDB = dbHelper.getWritableDatabase();
} catch (SQLiteReadOnlyDatabaseException e){
Log.d(TAG, "SQLiteReadOnlyDatabaseException");
startActivity(new Intent(context, MainActivity.class));
}
When the database is updated, it retains some connection with the old database. When new DBHelper(context) is executed again after database upgrade, the helper links back to the old database. Hence you need to close this class and reinitialize it. In my case MainActivity is the class that first initialized the dbHelper. So when the exception is caught and the activity is restarted, the connection to the old database is removed and it works fine.
Related
I am trying to better understand what it means to open an Sqlite database on a background thread in Android. Right now I am using a static/singleton pattern for my database via my class DatabaseHelper, so I only need to open it once, but I want to open it using good practice and understand why I shouldn't open it directly from within my Activity directly (or within the helper's constructor, for example).
My class:
public class DatabaseHelper extends SQLiteOpenHelper {
private static volatile SQLiteDatabase mDatabase;
private static DatabaseHelper mInstance = null;
private static Context mContext;
// ...
public static synchronized DatabaseHelper getInstance(Context context) {
/**
* use the application context as suggested by CommonsWare.
* this will ensure that you don't accidentally leak an Activity's
* context (see this article for more information:
* http://android-developers.blogspot.nl/2009/01/avoiding-memory-leaks.html)
*/
if (mInstance == null) {
mInstance = new DatabaseHelper(context.getApplicationContext());
}
return mInstance;
}
private DatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
mContext = context;
}
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DB_CREATE_SOME_TABLE); //some SQL expression
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(DB_ALTER);
}
public void open() throws SQLException {
mDatabase = getWritableDatabase();
}
public void close() {
mDatabase.close();
}
public boolean isOpen() {
return mDatabase.isOpen();
}
//below this would be various CRUD functions operating on mDatabase
// ...
// ...
}
Is it correct to say that you should do something like this:
DatabaseHelper mDatabaseHelper = DatabaseHelper.getInstance(this);
Thread thread = new Thread("OpenDbThread") {
public void run(){
mDatabaseHelper.open();
}
};
thread.start();
inside an Activity somewhere?
You're correct that the code you wrote would open the database on a background thread. However, you wouldn't actually the database was opened until thread.isAlive() returned false (or mDatabase.isOpen() returned true). Alternatively, you could make your Activity listen for a callback from your Thread.
I've seen a lot of ties between the onCreate and onOpen methods and SQLite database management in Android.
I am an iOS developer and I'm trying to "translate" (so to speak) my cocoa library so it could be used on Android. I need to create an SQLite database at runtime. I don't have an activity - since this is a library I'm creating. It seems I can't create a DB without an activity, is this correct ?
To create sqlite db, you don't basically need an activity in the library.it needs a context!!,
You can have a method in library/class which basically take a context in input/parameter and create database.
This context can be passed from application activity or service or receiver.
I don't see why you should not be able to open a database without an activity. You need to extend SQLiteOpenHelper.
public class MyDatabase {
private final DatabaseHelper databaseHelper;
private SQLiteDatabase db;
private static final String DATABASE_NAME = "com.my.db";
private static final int DATABASE_VERSION = 1;
public MyDatabase(Context context) {
databaseHelper = 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) {
//use db.execSQL to create the database
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// use db.execSQL to update (modify) the database
}
}
public SQLiteDatabase open() throws SQLException {
db = databaseHelper.getWritableDatabase();
return db;
}
}
i yet really grasp this whole context thing we found a lot in android programming. so i tried creating a function to drop all my tables, and here's a my partial code:
public class DBAdapter {
private static class DbHelper extends SQLiteOpenHelper {
private boolean databaseCreated = false;
public DbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public void deleteTables(){
Log.d("DBAdapter","dlm drop tables pre");
this.sqlDatabase.execSQL("DROP TABLE IF EXISTS ["+TABLE_TV+"];");
this.sqlDatabase.execSQL("DROP TABLE IF EXISTS ["+TABLE_CAMERA+"];");
this.sqlDatabase.execSQL("DROP TABLE IF EXISTS ["+TABLE_GPS+"];");
}
}
}
and the part where i'm going to call the function deleteTables
public class UpdateDatabase {
public void updateTable(String table,JSONObject jsonObject){
DBAdapter db = new DBAdapter(this);
db.deleteTables();
}
}
but of course it will return an error, since DBAdapter expects a context. public class UpdateDatabase is not an activity class. Calling DbAdapter db = new DBAdapter(this) from activity class will work just find. So how do I find any fix for this problem?
thanks
You can add a constructor to UpdateDatabase that takes a Context and stores it so that it is available to be used by updateTable. Something like this:
public class UpdateDatabase {
private final Context mContext;
public UpdateDatabase(Context context){
mContext = context;
}
public void updateTable(String table,JSONObject jsonObject){
DBAdapter db = new DBAdapter(mContext);
db.deleteTables();
}
}
Now, whenever you do new UpdateDatabase() you will need to do new UpdateDatabase(..context..) instead. If you are doing this from an Activity, then you can do new UpdateDatabase(this).
hi look this code..
public class DbManager
{
// the Activity or Application that is creating an object from this class.
Context context;
CustomSQLiteOpenHelper helper;
// a reference to the database used by this application/object
protected SQLiteDatabase db;
private static DbManager INSTANCE;
// These constants are specific to the database.
protected final String DB_NAME = "yourDB";
protected final int DB_VERSION = 1;
public DbManager(Context context)
{
this.context = context;
// create or open the database
helper = new CustomSQLiteOpenHelper(context);
this.db = helper.getWritableDatabase();
}
public static DbManager getInstance(Context context){
if(INSTANCE == null)INSTANCE = new DbManager(context);
return INSTANCE;
}
public void db_Close()
{
if(helper!=null){
helper.close();
}
this.db.close();
}
private class CustomSQLiteOpenHelper extends SQLiteOpenHelper
{
public CustomSQLiteOpenHelper(Context context)
{
super(context, DB_NAME, null, DB_VERSION);
}
#Override
public void onCreate(SQLiteDatabase db)
{
// This string is used to create the database.
// execute the query string to the database.
//db.execSQL(newTableQueryString);
Log.i("DataBaseManager", "Create Table");
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
// NOTHING TO DO HERE. THIS IS THE ORIGINAL DATABASE VERSION.
// OTHERWISE, YOU WOULD SPECIFIY HOW TO UPGRADE THE DATABASE.
}
}
}
// Inherit the DbManager Class
public class DataCollection extends DbManager {
public DataCollection(Context context){
super(context);
}
public void deleteTable(String TABLE_NAME){
try {db.execSQL("DROP TABLE "+TABLE_NAME);}//.delete(TABLE_NAME, null, null);}
catch (Exception e){
Log.e("DB ERROR", e.toString());
e.printStackTrace();
}
}
What is considered to be best practice for handling database connections? (I omitted the constructor and onUpgrade method within the DatabaseHelper class) These are just 2 ways I found on the internet, perhaps you have a better way of handling? I would love to hear.
Option 1
public class DatabaseManager {
private SQLiteDatabase mDb;
public DatabaseManager(Context context) {
DatabaseHelper helper = new DatabaseHelper(context);
helper.getWritableDatabase();
}
// ... methods that use mDb
private class DatabaseHelper extends SQLiteOpenHelper {
#Override
public void onCreate(SQLiteDatabase db) {
mDb = db;
//create database
}
#Override
public void onOpen(SQLiteDatabase db) {
mDb = db;
}
}
}
Option 2
public class DatabaseManager {
private DatabaseHelper mDbHelper;
public DatabaseManager(Context context) {
mDbHelper = new DatabaseHelper(context);
}
// ... methods that fetch the db
private void sampleMethod() {
SQLiteDatabase db = mDbHelper.getWritableDatabase();
//do stuff with database
mDbHelper.close();
}
private static class DatabaseHelper extends SQLiteOpenHelper {
#Override
public void onCreate(SQLiteDatabase db) {
//create database
}
}
}
Also, is it needed to call close() everytime you used the database within option 2? As for using option 1, I guess you need to call close() when the app's onDestroy is called?
I used to worry about all this but recently I started using ORMLite. It is a very light ORM with Android libraries and saves you worrying about this kind of stuff.
I would say that this could very soon become best practice as it removes a lot of repeated code when dealing with databases. It is also being updated regularly and the maintainer responds to queries very quickly.
I've implemented access to a database using SQLiteOpenHelper from the android.database package within some classes (with pattern DAO).
I wrote some junit tests for these classes using an AndroidTestCase but this causes the tests to use the same database as the application.
I read that the ProviderTestCase2 or RenamingDelegatingContext can be used to test the database separately. Unluckily I couldn't find any nice tutorial/example that shows how to test a database with ProviderTestCase2/RenamingDelegatingContext.
Can anyone point me somewhere OR give me some tip OR share some code for database testing?!
Cheeerrrrsss!!
Giorgio
Both the ProviderTestCase and RenamingDelegatingContext will destroy the database if one already exists before opening it within it's context, so in that sense they both have the same low-level approach towards opening a SQLite database.
You leverage this by opening the database in your fixture in setUp(), which will then ensure that your working with a fresh database before each test case.
I would suggest that you go for writing content providers rather than creating database adapters. You can use a common interface for accessing data, be it stored in the DB or somewhere over the network, the design of content providers can be accommodated to access such data at the cost of a bit of IPC overhead involved that most of us shouldn't have to care about.
If you did this for accessing a SQLite database, the framework would completely manage the database connection for you in a separate process. As added beef, the ProviderTestCase2<ContentProvider> completely bootstraps a test context for your content provider without you having to a write a single line of code.
But, that's not said it isn't such a huge effort to do the bootstrapping yourself. So supposing you had a database adapter as such; we'll just focus on open() for getting write access to our database, nothing fancy:
public class MyAdapter {
private static final String DATABASE_NAME = "my.db";
private static final String DATABASE_TABLE = "table";
private static final int DATABASE_VERSION = 1;
/**
* Database queries
*/
private static final String DATABASE_CREATE_STATEMENT = "some awesome create statement";
private final Context mCtx;
private SQLiteDatabase mDb;
private DatabaseHelper mDbHelper;
private static class DatabaseHelper extends SQLiteOpenHelper {
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE_STATEMENT);
}
#Override
public void onUpgrade(SQLiteDatabase db, int a, int b) {
// here to enable this code to compile
}
}
/**
* Constructor - takes the provided context to allow for the database to be
* opened/created.
*
* #param context the Context within which to work.
*/
public MyAdapter(Context context) {
mCtx = context;
}
/**
* Open the last.fm database. If it cannot be opened, try to create a new
* instance of the database. If it cannot be created, throw an exception to
* signal the failure.
*
* #return this (self reference, allowing this to be chained in an
* initialization call)
* #throws SQLException if the database could be neither opened or created
*/
public MyAdapter open() throws SQLException {
mDbHelper = new DatabaseHelper(mCtx);
mDb = mDbHelper.getWritableDatabase();
return this;
}
public void close() {
mDbHelper.close();
}
}
Then you could write your test as such:
public final class MyAdapterTests extends AndroidTestCase {
private static final String TEST_FILE_PREFIX = "test_";
private MyAdapter mMyAdapter;
#Override
protected void setUp() throws Exception {
super.setUp();
RenamingDelegatingContext context
= new RenamingDelegatingContext(getContext(), TEST_FILE_PREFIX);
mMyAdapter = new MyAdapter(context);
mMyAdapter.open();
}
#Override
protected void tearDown() throws Exception {
super.tearDown();
mMyAdapter.close();
mMyAdapter = null;
}
public void testPreConditions() {
assertNotNull(mMyAdapter);
}
}
So what's happening here is that the context implementation of RenamingDelegatingContext, once MyAdapter(context).open() is called, will always recreate the database. Each test you write now will be going against the state of the database after MyAdapter.DATABASE_CREATE_STATEMENT is called.
I actually use database with SQLiteOpenHelper and I have a trick for testing.
The idea is to use standard on-file stored DB during the normal use of the app and an in-memory DB during tests. In this way you can use a clear DB for each test without insert/delete/update data in your standard DB. It works fine for me.
Keep in mind you can use in-memory database, just passing null as name of database file. This is clearly documented in the API documentation.
Advantages of using in-memory DB during tests is explained here:
https://attakornw.wordpress.com/2012/02/25/using-in-memory-sqlite-database-in-android-tests/
In my project I have the DBHelper class wich extends SQLiteHelper. As you can see, there are the standard methods. I simply added a constructor with two parameters. The difference is that when I call the super constructor, I pass null as DB name.
public class DBHelper extends SQLiteOpenHelper {
public static final int DATABASE_VERSION = 1;
public static final String DATABASE_NAME = "mydatabase.db";
public DBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public DBHelper(Context context, boolean testMode) {
super(context, null, null, DATABASE_VERSION);
}
public void onCreate(SQLiteDatabase db) {
//create statements
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//on upgrade policy
}
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//on downgrade policy
}
}
Every "model" in the project extends DBModel that is an abstract class.
public abstract class DBModel {
protected DBHelper dbhelper;
public DBModel(Context context) {
dbhelper = new DBHelper(context);
}
//other declarations and utility function omitted
}
As discussed here: How can I find out if code is running inside a JUnit test or not?
there is a way to establish if you are running JUnit tests, simply searching in stack trace elements.
As a conseguence, I modified DBModel constructor
public abstract class DBModel {
protected DBHelper dbhelper;
public DBModel(Context context) {
if(isJUnitTest()) {
dbhelper = new DBHelper(context, true);
} else {
dbhelper = new DBHelper(context);
}
}
private boolean isJUnitTest() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
List<StackTraceElement> list = Arrays.asList(stackTrace);
for (StackTraceElement element : list) {
if (element.getClassName().startsWith("junit.")) {
return true;
}
}
return false;
}
//other declarations and utility function omitted
}
Note that
startsWith("junit.")
may be
startsWith("org.junit.")
in your case.
I have an application that uses a ContentProvider backed by an sqlite database to provide data to the application.
Let PodcastDataProvider be the actual dataprovider used by the application.
Then you can set up a test provider with something like the following:
public abstract class AbstractPodcastDataProvider extends ProviderTestCase2<PodcastDataProvider>{
public AbstractPodcastDataProvider(){
this(PodcastDataProvider.class, Feed.BASE_AUTH);
}
public AbstractPodcastDataProvider(Class<PodcastDataProvider> providerClass,
String providerAuthority) {
super(providerClass, providerAuthority);
}
public void setUp() throws Exception{
super.setUp();
//clear out all the old data.
PodcastDataProvider dataProvider =
(PodcastDataProvider)getMockContentResolver()
.acquireContentProviderClient(Feed.BASE_AUTH)
.getLocalContentProvider();
dataProvider.deleteAll();
}
}
to setup a test data provider that will be backed by a different database than the actual application.
To test the DAO, create another class which extends AbstractPodcastDataProvider and use the
getMockContentResolver();
method to get an instance of a content resolver that will use the test database instead of the application database.
private static String db_path = "/data/data/android.testdb/mydb";
private SQLiteDatabase sqliteDatabase = null;
private Cursor cursor = null;
private String[] fields;
/*
* (non-Javadoc)
*
* #see dinota.data.sqlite.IDataContext#getSQLiteDatabase()
*/
public SQLiteDatabase getSQLiteDatabase() {
try {
sqliteDatabase = SQLiteDatabase.openDatabase(db_path, null,
SQLiteDatabase.OPEN_READWRITE);
sqliteDatabase.setVersion(1);
sqliteDatabase.setLocale(Locale.getDefault());
sqliteDatabase.setLockingEnabled(true);
return sqliteDatabase;
} catch (Exception e) {
return null;
}
}
if you give the exact location of the sqlite db(in my case it's db_path), using the above method you can find-out whether it returns an sqlitedatabase or not.
A possible solution can be to open database using this method
myDataBase = SQLiteDatabase.openDatabase(DATABASE_NAME, null, SQLiteDatabase.OPEN_READWRITE);
And change database name in your tests. Here you can find some info about this method.