My app uses a preloaded and copied from /assets to data/data ( Ship an application with a database) db which is simply a single table of product data and is 'read only' as users do not save to the DB. If I add more products to the DB table I need to get this to existing users. If I update my app with the new DB will the update process delete the old DB that was copied from the assets dir to data/data thereby allowing the 'DBexists' check to fail on first running the updated version thus triggering copying of the new DB from /assets to data/data?
Short answer, yes, if you put the following snippet it the onUpgrade() method:
try {
copyDataBase("database.db");
} catch (IOException e) {
Log.w(TAG, e);
}
It may be worth deleting the db file in copyDataBase() before writing over it, just to make it less likely to corrupt.
NB: this uses the implementation as used in the accepted answer of the question you linked.
Off the top of my head the only thing I can think of is to slightly abuse the onUpgrade() method in your SQLiteOpenHelper implementation, and change the database by making a call to this with a new version number and having it do whatever you need it to do in that method.
Saying that, I don't really know much about the Android app update process, so this could be way off.
Related
In Android Pie sqlite Write-Ahead logging (WAL) has been enabled by default. This is causing errors for my existing code only in Pie devices.
I have been unable to turn off WAL successfully using SQLiteDatabase.disableWriteAheadLogging() or PRAGMA journal_mode due to the way I access the database. I would like to disable WAL completely with an Android setting called db_compatibility_wal_supported :
Compatibility WAL (Write-Ahead Logging) for Apps
Does anyone know how to configure this? I don't know if this file can be altered programmatically at startup or if it is changed manually.
Further Details about the problem
I have a sqlite database (20mb+ / 250k records) in my app. This db is generated using plain java on my server. It contains a food database and the user of the app can add to the database (and the server is updated). This is stored in the assets folder in android.
During first installation the database is copied from assets to the app folder so that it can be written to, using effectively this method :
Copy SQLite database from assets folder
Unfortunately, once I start writing to the database using SqlDroid wal is enabled and the tables which were in the original db have vanished and only any newly created tables remain. The size of the database however is still 20mb+. All the database errors are due to the missing tables.
The table copying and writing method works perfectly in versions of Android prior to Pie.
The best and simplest way to disable WAL mode in your Database is as follows:
public class MyDbHelper extends SQLiteOpenHelper {
//...
#Override
public void onOpen(SQLiteDatabase db) {
db.disableWriteAheadLogging(); // Here the solution
super.onOpen(db);
}
//...
}
This way, all access to your database will be with WAL mode disabled. As much as you open and close multiple connections throughout the implementation of your App
If you are using Room, you won't have direct access to the database but you can instead set the journal mode when you are building/opening the database:
db = Room.databaseBuilder(context, Database.class, "Database")
.setJournalMode(JournalMode.TRUNCATE)
.build();
one cannot use SQLDroidDriver.ADDITONAL_DATABASE_FLAGS, simply because there is no constant available, which would negate flag ENABLE_WRITE_AHEAD_LOGGING.
WAL can still be disabled by creating either of these scenarios:
a) set flag OPEN_READONLY (applies to situations where R/O access does suffice).
b) run PRAGMA journal_mode=DELETE as the first query, in order to override PRAGMA journal_mode=WAL.
c) file an issue against SQLDroidConnection.java,
in order to have .enableWriteAheadLogging() and .disableWriteAheadLogging() supported on the driver-level.
#Rockvole please share error that you are facing, that help us to find appropriate solution.
Mean while, i understand that you want to close that WAL in android pie and you are using "SQLDroid" lib to create Sqlite DB.
This lib internally using "SQLiteDatabase" to store data locally, I think you need to call "SQLiteDatabase.disableWriteAheadLogging()" in "SQLiteDatabase" class where DB instance created the package name is "package org.sqldroid;"
or Get internal SQLiteDatabase instance and call disableWriteAheadLogging().
Second solution is create "config.xml" inside values folder and wirte "<bool name="db_compatibility_wal_supported">false</bool>" and run and check its work.
I finally found the answer. It seems that the database errors I was receiving is not directly related to WAL. It is because the widely used code to copy a database from assets has a bug in it where the database is left open during the copy operation. This only started causing a problem in Android P.
The solution is to close the database after you get the database file name.
SQLiteDatabase destinationDatabase = sqLiteOpenHelper.getWritableDatabase();
String dbFileName=destinationDatabase.getPath();
destinationDatabase.close();
// Now write the destinationDatabase using the dbFileName
This is detailed more here :
Android P - 'SQLite: No Such Table Error' after copying database from assets
I know there are a lot of similar questions and I hope not being repeating but I tried different things and I am still unable to fix the issue. I am developing an Android app that uses SQLite to store some data internally. I have been using this app in my own phone for a month and I added lot of data I do not want to lose, but at the same time I have been improving the app in my computer, but the improvements made me change some column names, adding some columns and tables. So, right now I want to update the app in my phone.
The previous version had the database set to 2 and the new one is 3. In addition, my app has the ability to create backups of the database just copying the file to the SDcard, and the ability to restore it by replacing the internal database file for one that the user selects from the sd card. Now the problem is that I did a backup of the database from the phone and I moved it to my computer to import it in the new version of the app running in an emulator to ensure the app can upgrade the database as expected before I install the new version on my phone. The problem is that onUpgrade for the SQLiteOpenHelper is never called. I did different trials but nothing is working. I can ensure the database from my phone has version 2 (I used sqliteadmin "PRAGMA user_version;" SQL query and answers 2) and my app looks for version 3. The app crashes as it is unable to find the new tables.
Here you have the code executed when importing a backup:
private boolean importDatabase(String path) {
String dbPath = mDbHelper.getDbPath();
mDbHelper.close();
boolean result = copyFile(path, dbPath);
if (result) {
mDbHelper = new DbHelper(this);
mDbHelper.getWritableDatabase().close();
}
return result;
}
I have to mention as well that when the user reaches the place to import/export the database in the new version, a new empty database with version 3 has been created as the app needs an active database from the stating activity. Then, when importing, this empty database file is replaced by the imported one. I wonder if there is any cached data I am missing to clean.
Could you give me some clues to understand why onUpgrade is never called in this scenario?
I have an app that creates a database and do some stuff. I am wondering if i upload a new db to a server and download it to the exact folder where the older one exists it will be overwritten and i am good to go? Or there will be a problem. Assuming it has the same name, same column names, etc. Of course i am reffering to sqlite.
In Android, when performing a database update you should be using onUpgrade inside of the SQLiteOpenHelper. One way of doing this is to download text files that include the sql instructions needed to modify the current database or update rows with new data. The reason you have to do this is because Android will only create the database once. After the initial creation the call to onCreate for the database will not occur.
When Android opens an SQLite file, and the file is corrupt, Android deletes the file.
As surprising as it may sound, this behavior is implemented clearly in the Android source code, leading to consternation and to this Android issue.
Anyway, as app developers we just have to deal with it. What is the best strategy when opening an SQLite file?
Corrupt files are actually often recoverable, so we can't afford to take any risk of losing one of those corrupt files.
Creating a backup before opening is very time-costly, and would make the app startup really slow, so anything smarter would be greatly appreciated.
The issue has been fixed starting from API level 11. Now there exists an interface: DatabaseErrorHandler which you can implement to define your own onCorruption() method. At the opening of your database you can pass this DatabaseErrorHandler as a parameter to the constructor of SQLiteOpenHelper.
e.g.
public class MyDbErrorHandler implements DatabaseErrorHandler {
#Override
onCorruption(SQLiteDatabase db) {
// Back up the db or do some other stuff
}
}
SQLiteOpenHelper dbHelper = new SQLiteOpenHelper(context, "MyDbName", null, 1,
new MyDbErrorHandler());
SQLiteDatabase db = dbHelper.getWritableDatabase();
For Systems with an API level below 11 and for those who dont want to use this approach there are several alternatives.
1. Android data backup
Android offers a backup service which automatically copys the application data to a remote 'cloud' storage. If a database gets corrupted or the application is reinstalled after factory reset. The application data can be restored from the remote data.
For further information see: http://developer.android.com/guide/topics/data/backup.html
2. JDBC (sqldroid)
One approach could be implementing your own database connector, either native JDBC or with the sqldroid library. It is officially not supported by google and you cannot be sure whether it will be still available in future Android versions.
3. Berkley DB Java Edition
An interesting approach, also with a look to performance handling large data amounts, is the Berkley DB Java Edition.
Here is a tutorial how to use it in Android: http://download.oracle.com/docs/cd/E17277_02/html/HOWTO-Android.html
4. Customizing the android libraries
Another more risky approach is to implement your own database class by copying or extending the SQLiteDatabase.java from the android source and reimplement or override the critical parts which are:
public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags) {
SQLiteDatabase sqliteDatabase = null;
try {
// Open the database.
sqliteDatabase = new SQLiteDatabase(path, factory, flags);
if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
sqliteDatabase.enableSqlTracing(path);
}
if (SQLiteDebug.DEBUG_SQL_TIME) {
sqliteDatabase.enableSqlProfiling(path);
}
} catch (SQLiteDatabaseCorruptException e) {
// Try to recover from this, if we can.
// TODO: should we do this for other open failures?
Log.e(TAG, "Deleting and re-creating corrupt database " + path, e);
EventLog.writeEvent(EVENT_DB_CORRUPT, path);
if (!path.equalsIgnoreCase(":memory")) {
// delete is only for non-memory database files
new File(path).delete();
}
sqliteDatabase = new SQLiteDatabase(path, factory, flags);
}
ActiveDatabases.getInstance().mActiveDatabases.add(
new WeakReference<SQLiteDatabase>(sqliteDatabase));
return sqliteDatabase;
}
and:
/* package */ void onCorruption() {
Log.e(TAG, "Removing corrupt database: " + mPath);
EventLog.writeEvent(EVENT_DB_CORRUPT, mPath);
try {
// Close the database (if we can), which will cause subsequent operations to fail.
close();
} finally {
// Delete the corrupt file. Don't re-create it now -- that would just confuse people
// -- but the next time someone tries to open it, they can set it up from scratch.
if (!mPath.equalsIgnoreCase(":memory")) {
// delete is only for non-memory database files
new File(mPath).delete();
}
}
}
The dangerous part about that is, that you also would have to reimplement the helper classes that access the SQLiteDatabase such as SQLiteOpenHelper. Since the SQLiteDatabase class uses factory methods you could face unexpected side effects.
I faced the same issue and asked a question about it here. I regularly backup my database to the SD-card, but I cannot recommend it. It seems as if a database that is copied from SD-cards used in newer Android phones is considered corrupt after the copy is completed on the older versions of SQLite that is still used on android 2.3.6.
If your database is small enough then I would recommend keeping a backup, but keep it on the internal memory. Unless it would anger your users, do not enable the "install to sd-card"-option, I believe it is correlated to the issue. After these precautions your database should be relatively safe.
About the slower start time: I do my backups in a background thread when the app is closed, this is the most likely time that the phone has to do some background work without troubling the user.
Not for at opening every time, But I think whenever our database make a changes or upgrades at that time make copy of DB files for back-up is the one of the solutions.
Also if possible to use SQLite source and modifies it and use it in our application with JNI or Library, then we can achieve it.
Thanks.
A simple solution to this problem would be to replicate the DB entirely.
For example in the on Destroy method of your app. Copy the DB on every Destroy, when the main db is corrupted (and deleted by android) you can switch to the backup db.
I've been going through some tutorials on working with sqlite databases and they all seem to create a new database, tables, etc on the first run of the application. Is this necessary? What if I already have a pre-built database sitting in the assets folder when the application is installed? Can I not simply just open the connection to said database and start using its information or is there a specific reason everyone wants to create it using sql on first launch?
This question comes up frequently. Try this tutorial to use an existing database on Android:
http://www.reigndesign.com/blog/using-your-own-sqlite-database-in-android-applications/
You can't use database file which sits in assets folder directly as SQLite database, since this file would not be usual file located in common filesystem. E.g. you can have only readonly access to it. So the only your option is to copy those database from assets folder to device's filesystem.
To handle database creation for the first time and accessing it there's special helper class SQLiteOpenHelper. Read about it here. Specifically look in SQLiteOpenHelper.onCreate() - where should sit database creation (or copying from assets folder as in your case)
It's been quite sometime since I last worked with SQLite databases (in Android) but I believe that when they write CREATE statements, they always do so with the IF NOT EXISTS condition (i.e., CREATE (DATABASE|TABLE) IF NOT EXISTS...).
I don't know what you'll use SQLite for but I believe they do that in Android "just to make sure". That is, if it's the user's first time to run the app, the DB/Tables must be created first else app goes bonkers. Otherwise, they are (probably) created already and this case will be handled by the IF NOT EXISTS clause and they just go ahead and create a connection with the existing DB. Win-win.
(If, for some reason, it is not the user's first time to use the app and the DB isn't there, it will just be created again. But that's obvious isn't it? ;) )
If you just link with preexisting database it wont bind to your system. So there may be failures. Creating db at first run is the most appropriate way to work with db.