0

Whenever I update my database I get this error. But when I rerun the app as it is, the database gets updated.

android.database.sqlite.SQLiteReadOnlyDatabaseException: attempt to write a readonly database (code 1032)[

Code:

 public DBAdapter(Context context) {
    super(context, DATABASE_NAME, null, DATABASE_VERSION);
    ctx = context;
    db = getWritableDatabase();
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    if (oldVersion != newVersion) {
        ctx.deleteDatabase(DATABASE_NAME);
        new DBAdapter(ctx);
    } else {
        super.onUpgrade(db, oldVersion, newVersion);
    }
}

As one of the SO answers suggested, I have added this too:

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

BTW: I am using SQLiteAssetHelper to create prebuilt database

2
  • why you want to delete all database? Commented Jul 17, 2016 at 8:11
  • I want to send lots of prebuilt data. Commented Jul 17, 2016 at 8:48

2 Answers 2

1

This is not a solution to prevent this issue but a work around.

 public DBAdapter(Context context) {
    super(context, DATABASE_NAME, null, DATABASE_VERSION);
    ctx = context;
    try {
        db = getWritableDatabase();
    } catch (SQLiteReadOnlyDatabaseException e){
        ctx.startActivity(new Intent(ctx, MainActivity.class));
    }
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    if (oldVersion != newVersion) {
        ctx.deleteDatabase(DATABASE_NAME);
        new DBAdapter(ctx);
    } else {
        super.onUpgrade(db, oldVersion, newVersion);
    }
}

First time when the adapter is initialized, a writable db is created. Then onUpgrade gets called. Here when the database is deleted, the adapter get reinitialized. But the connection of the db is not deleted and persists hence, the second time when db = getWritableDatabase(); is executed SQLiteReadOnlyDatabaseException occurs. The original activity that initialized DBAdapter is restarted. Now the Adapter is reinitialized and the onUpgrade method is not called and hence SQLiteReadOnlyDatabaseException does not occur.

All this process happens very fast and the user experience does not become bad in my case.

Note: new DBAdapter(ctx); does not seem to be necessary and deleteDatabase seems to recreate the adapter. But for caution, I have written this line of code.

I would love to get some information on the cause and solution for this error.

Sign up to request clarification or add additional context in comments.

Comments

1

I had some similar issues with Android SQLite databases. I submitted a bug report on it long ago at https://code.google.com/p/android/issues/detail?id=174566. This report discusses my findings on the reasons in more detail. I am not sure if it is related to your issue or not, but it seems to share some characteristics.

To summarize here, my debugging indicated that Android opens the database file, calls onUpgrade(), and if you replace the database file during the onUpgrade() call, the Android side file handle points to the old file and thus causes the app to crash when you return from onUpgrade() and Android tries to access the old file.

Here is some code I used to get around the issue:

When the app starts, I did this in onCreate():

Thread t = new Thread(new Runnable() {
  @Override
  public void run() {
    Context context = getApplicationContext();
    DBReader.copyDB(MainActivity.this);
    DBReader.initialize(context);
  }
});
t.start();

This causes the update of the database file to happen in the background while the app is starting and user is occupied with awe of the awesome application. Because my file was rather big and it took a while to copy. Notice that I completely avoid doing anything in onUpgrade() here.

DBReader is my own class, for which the main code of interest is this:

  SharedPreferences prefs = context.getSharedPreferences(Const.KEY_PREFERENCES, Context.MODE_PRIVATE);
  //here we have stored the latest version of DB copied
  String dbVersion = prefs.getString(Const.KEY_DB_VERSION, "0");
  int dbv = Integer.parseInt(dbVersion);
  if (checkIfInitialized(context) && dbv == DBHelper.DB_VERSION) {
    return;
  }
  File target = context.getDatabasePath(DBHelper.DB_NAME);
  String path = target.getAbsolutePath();
  //Log.d("Awesome APP", "Copying database to " + path);

  path = path.substring(0, path.lastIndexOf("/"));
  File targetDir = new File(path);
  targetDir.mkdirs();
  //Copy the database from assets
  InputStream mInput = context.getAssets().open(DBHelper.DB_NAME);
  OutputStream mOutput = new FileOutputStream(target.getAbsolutePath());
  byte[] mBuffer = new byte[1024];
  int mLength;
  while ((mLength = mInput.read(mBuffer)) > 0) {
    mOutput.write(mBuffer, 0, mLength);
  }
  mOutput.flush();
  mOutput.close();
  mInput.close();
  SharedPreferences.Editor edit = prefs.edit();
  edit.putString(Const.KEY_DB_VERSION, "" + DBHelper.DB_VERSION);
  edit.apply();

and the code for checkIfInitialized():

public static synchronized boolean checkIfInitialized(Context context) {
  File dbFile = context.getDatabasePath(DBHelper.DB_NAME);
  return dbFile.exists();
}

So, to make the story short, I just avoided onUpgrade() alltogether and implemented my own custom upgrade functionality. This avoids the problem of the Android OS crashing on old and invalid filehandles caused by change of the database in onUpgrade().

Kind of odd, I though, for the onUpgrade() to cause the OS to crash your app if you actually end up upgrading your database file in a function intended to let you upgrade your database. And the Google comments on the bug report were made few years after so I no longer had the original crashing code around for easy proof of concept.

Your problem might be slightly different in that you are not copying the database file, but you still seem to be modifying it, so the root cause might be similar.

1 Comment

My problem is exactly the same as yours. Your solution is good when error is to be avoided. I found that catching the error and reinitializing the class with new context (not reusing the old context) solved the problem easily.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.