I'm hoping that somebody may be able to help me with something that I'm stuck on. I've searched StackOverflow looking at some other solutions and nothing is working so far.
I'm using a ContentProvider, CursorLoader, and SQLiteOpenHelper to get data out of a SQLite database into some ListViews. This has been working fine for a long time. I'm able to make updates, deletions, etc. to the data and the changes are reflected in the ListViews thanks to some use of setNotificationUri on the cursors and ContentResolver's notifyChange function.
Recently, I've implemented a cloud backup feature for my app, where the database file is backed up to cloud storage for later retrieval. At this point, I run into problems.
I have no trouble bringing the database back down from the cloud and replacing the local database file with the inbound version. However, after doing this, my Cursors and Loaders continue to return data as it existed before the database file is replaced. If I exit the application and wait for its processes to end, then restart it, the new data then appears.
I know that Cursors and Loaders store data across the Activity lifecycle, so I believe this to be the source of my trouble, but any attempts I've made at invalidating this caching behaviour has failed.
What I've checked so far:
Verified that onCreateLoader is in fact getting called after the database swap and the CursorLoader is getting recreated. It is.
Verified that the new Cursor is getting swapped into my CursorAdapter via swapCursor. It is.
Double-checked that I was using the correct URI values when I tried using notifyChange. URI values matched what I use when I update the data for a particular Listview for normal CRUD operations.
The only thing that has worked is to recreate my SQLiteOpenHelper object before every query, rather than relying on the instance created in my ContentProvider onCreate function. I assume though that this is bad practice and would create a scenario where too many connections are opened as I understand that every SQLiteOpenHelper instance manages its own connection.
Any help appreciated. Some relevant code snippets...
DBHelper.java
public class DBHelper extends SQLiteOpenHelper {
public DBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
...
public void ReplaceDatabase(Context c) throws IOException
{
// First, let's make sure the database is closed...
this.close();
File inboundFile = new File(c.getFilesDir(), this.INCOMING_DATABASE_NAME);
if(!inboundFile.exists()) {
throw new IOException("Incoming Database doesn't exist.");
}
File currentFile = c.getDatabasePath(this.DATABASE_NAME);
if(!currentFile.exists()) {
throw new IOException("Database doesn't exist.");
}
boolean deletionResult = c.deleteFile(this.BACKUP_DATABASE_NAME);
try {
InputStream input = new FileInputStream(currentFile);
File backupFile = new File(c.getFilesDir(),this.BACKUP_DATABASE_NAME);
OutputStream output = new FileOutputStream(backupFile);
byte[] buffer = new byte[1024];
int length;
while ((length = input.read(buffer))>0)
{
output.write(buffer, 0, length);
}
output.flush();
output.close();
input.close();
} catch (FileNotFoundException e) {
throw e;
}
try {
InputStream input = new FileInputStream(inboundFile);
currentFile.delete();
File databaseFile = c.getDatabasePath(this.DATABASE_NAME);
OutputStream output = new FileOutputStream(databaseFile);
byte[] buffer = new byte[1024];
int length;
while ((length = input.read(buffer))>0)
{
output.write(buffer, 0, length);
}
output.flush();
output.close();
input.close();
} catch (FileNotFoundException e) {
throw e;
}
c.getContentResolver().notifyChange(Asset.AssetTable.CONTENT_DETAIL_URI, null);
}
}
CoreProvider.java
public class CoreProvider extends ContentProvider {
private DBHelper dbHelper;
#Override
public boolean onCreate() {
dbHelper = new DBHelper(getContext());
return true;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder){
// Here I build the query. Then....
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor c = qb.query(
db, // The database to query
projection, // The columns to return from the query
selection, // The columns for the where clause
selectionArgs, // The values for the where clause
null, // don't group the rows
null, // don't filter by row groups
orderBy // The sort order
);
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
}
Related
I want to create the sqlite database file DatabaseName.db with few entities that should be created in path of application (/data/data/MyApplicationName/databases/DatabaseName.db) when I try to execute the snippet code bellow, however the DatabaseName.db file is not there. Why ?
MyDatabaseSample db = Room.databaseBuilder(context,
MyClass.class,
"DatabaseName.db")
.addCallback(new RoomDatabase.Callback() {
#Override
public void onCreate(#NonNull SupportSQLiteDatabase ssdb) {
super.onCreate(db);
Log.d(TAG, "Database created - populate database");
}).build();
The database is created in the path of application only if I create an instance of a entity object and insert it in database right after get the database reference db. As I want to pre-populate database just after database creation, I think just make sense do it inside onCreate method of callback, but onCreate will never be called. So, How can I create the "DatabaseName.db" file with all tables representing entities and populate the database using callback ?
OBS: I am using Room version use 1.1.0-alpha2 and compiling with SDK android API 27.
I think you need to define some Room entities before pre-populate the db that's what i have done and it works just as expected, here is some code of what i have done so far:
public class DatabaseCreator {
private static MyDatabaseSample appDatabase;
private static final Object LOCK = new Object();
public synchronized static MyDatabaseSample getDBInstance(final Context context){
if(appDatabase == null) {
synchronized (LOCK) {
if (appDatabase == null) {
RoomDatabase.Callback appDBCallback = new RoomDatabase.Callback() {
#Override
public void onCreate(#NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
try {
ReadScript.insertFromFile(context, R.raw.populate_db, db);
} catch (IOException e) {
Log.d("DB Population Error", e.toString());
}
}
};
appDatabase = Room.databaseBuilder(context,
MyDatabaseSample.class, "DatabaseName").addCallback(appDBCallback).build();
}
}
}
return appDatabase;
}
}
The code above is a singleton that uses the Callback's onCreate to pre-populate the db using a "raw resource" (To add raw resources to your project just create a folder inside your res folder like this "res/raw") that contains an sql script. To read the script i have used this code:
public class ReadScript {
public static int insertFromFile(Context context, int resourceCode, SupportSQLiteDatabase db) throws IOException {
// Reseting Counter
int result = 0;
// Open the resource
InputStream insertsStream = context.getResources().openRawResource(resourceCode);
BufferedReader insertReader = new BufferedReader(new InputStreamReader(insertsStream));
// Iterate through lines (assuming each insert has its own line and theres no other stuff)
while (insertReader.ready()) {
String insertStmt = insertReader.readLine();
if(insertStmt != null){
if(insertStmt.isEmpty()) continue;
db.execSQL(insertStmt);
result++;
Log.d("Statement #", Integer.toString(result));
}
}
insertReader.close();
// returning number of inserted rows
return result;
}
}
And then you just create the db instance by doing:
MyDatabaseSample db = DatabaseCreator.getDBInstance(getContext());
Note: You can try to create tables inside the raw script but i haven't tried it yet.
Goog luck.
I am including a database in my Android application. I am following the guide here:
http://developer.android.com/training/basics/data-storage/databases.html
I have a "DatabaseHelper" class that extends SQLiteOpenHelper as suggested, and I have a Contract that holds the information about the table and implements 'BaseColumns'. Therein, I have a function for inserting a new row into the database:
public static void addUploadInfo(SQLiteOpenHelper dbHelper, UploadInfo info) {
// Gets the data repository in write mode
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = info.getContentValues();
// Insert the new row, returning the primary key value of the new row
long newRowId;
newRowId = db.insert(
UploadInfoTable.TABLE_NAME,
null,
values);
db.close(); // Closing database connection
info.setDatabaseId(newRowId);
}
I am using the database to store app related meta data about a file. When I create a new file, I run the above function, and a row is successfully added to the database (and the database is created successfully if needed). I can see it using:
public static List<UploadInfo> getUploadInfoList(SQLiteOpenHelper dbHelper) {
List<UploadInfo> infoList = new ArrayList<>();
SQLiteDatabase db = dbHelper.getReadableDatabase();
String allQuery = "SELECT * FROM " + UploadInfoTable.TABLE_NAME;
Cursor cursor = db.rawQuery(allQuery, null);
if (cursor.moveToFirst()) {
do {
infoList.add(new UploadInfo(cursor));
} while (cursor.moveToNext());
}
cursor.close();
db.close();
return infoList;
}
However, when the app first loads, I insert a few initial entries into the database using:
#Override
public void onResume() {
super.onResume();
DatabaseHelper dbHelper = new DatabaseHelper(getActivity());
for (UploadInfo info: uploadList) {
UploadInfoTable.addUploadInfo(dbHelper, info);
}
}
These entries are assigned a new row ID that appears to be the correct value (not -1 indicating an error). There are no errors in the log. However, they are not found when I next run getUploadInfoList.
I have also tried this alternate insert function:
public static void addUploadInfo(SQLiteOpenHelper dbHelper, UploadInfo info) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = info.getContentValues();
// Insert the new row, returning the primary key value of the new row
db.beginTransaction();
long newRowId = -1;
try {
newRowId = db.insertOrThrow(
UploadInfoTable.TABLE_NAME,
null,
values);
db.setTransactionSuccessful();
} catch (Exception e) {
e.printStackTrace();
} finally {
db.endTransaction();
}
db.close(); // Closing database connection
info.setDatabaseId(newRowId);
}
but I see the same result. In no case is info null. I always log the contents of the ContentValues. I use final variables for column names, so don't think I have an error in column name. This would show up as a row value of -1 anyway, and it doesn't.
Why would one implementation of the code insert successfully and the other not?
I suspect your code in onResume() is not successfully detecting the upgrade. Rather put all your code having to do with the upgrade in onUpgrade() instead of trying to detect an upgrade in onResume(). That should resolve the issue.
I have: Accounts.java
public class Accounts{
private SQLiteDatabase dbConfig;
public Cursor list(Context context, String db, String where, String order) {
DBHelper dbHelper = new DBHelper(context, db);
dbConfig = dbHelper.getReadableDatabase();
Cursor c = dbConfig.query("accounts", new String[]{ "iId","sName"}, where, null, null, null, order);
return c;
}
}
and: MainActivity.java
Accounts account = new Accounts();
Cursor cursor = account.list(getApplicationContext(), globalDB, null, null);
while (cursor.moveToNext()) {
int id = cursor.getInt(0);
String name = cursor.getString(1);
}
cursor.close();
Running my application I get some logcat messages like:
close() was never explicitly called on database...
What is the best way to prevent it? How can I close a Cursor that returns from other class?
Thanks
After account.list() exits, the underlying database connection is still open, but there are no remaining references to it. So, it has leaked.
You can close the database once you're finished with it, or you can keep it open for the lifetime of your application by having a single global database connection which you open once, share amongst all your activities, and never close.
I recommend the latter approach. It results in much simpler code.
instead of calling cursor.close(); you can call and create a Accounts.close() method that closes both cursor and database
My application has a SQLite database to contain a list of highways. It shows them all in a list. It first tries to query the database for the roads. If the query doesn't return any, it calls another method that downloads a list from a remote server, and populates the database. After that returns, it immediately queries the database again.
That's how it should work. How it actually works is, the first query always returns nothing. It goes straight to downloading a fresh list, it inserts the list into the database, and queries again. The second query always returns the correct result. The strange thing is, I can repeat the operation without even exiting the application. Using the adb shell, I can read the SQLite3 database on the emulator. The data shown in the database is exactly as expected. But the application is behaving as though the data isn't there? Is there some behaviour I'm not aware of? Here's the code.
RoadsDataSource.java
public class RoadsDataSource {
private DataStorage data;
private static Context context;
public RoadsDataSource() {
this.data = new DataStorage(RoadsDataSource.context);
}
private List<Road> getRoads(Integer state) {
List<Road> roads = loadRoadsFromDb(state);
if (roads.isEmpty()) {
Request api = new Request(RoadsDataSource.context);
Roads apiRoads = api.fetchRoads(state);
this.data.storeRoads(apiRoads);
roads = loadRoadsFromDb(state);
}
return roads;
}
private List<Road> loadRoadsFromDb(Integer state) {
SQLiteQueryBuilder query = new SQLiteQueryBuilder();
query.setTables(Queries.ROAD_STATE_MATCHES);
Cursor results = query.query(
this.data.getWritableDatabase(),
new String[] {Tables.ROADS + "." + Tables.Roads.ID, Tables.ROADS + "." + Tables.Roads.TYPE, Tables.ROADS + "." + Tables.Roads.NUMBER},
Queries.ROADS_BY_STATE,
new String[] {state.toString()}, null, null, null
);
List<Road> roads = new ArrayList<Road>();
results.moveToFirst();
while (!results.isAfterLast()) {
roads.add(new Road(results.getInt(0), results.getString(1), results.getInt(2)));
results.moveToNext();
}
results.close();
System.out.println(roads.size());
return roads;
}
}
DataStorage.java
public class DataStorage extends SQLiteOpenHelper {
public void storeRoads(Roads roads) {
SQLiteDatabase db = this.getWritableDatabase();
for (Road road : roads.getRoads()) {
ContentValues roadRow = new ContentValues();
roadRow.put(Tables.Roads.ID, road.getId());
roadRow.put(Tables.Roads.TYPE, road.getType());
roadRow.put(Tables.Roads.NUMBER, road.getNumber());
try {
db.insertOrThrow(Tables.ROADS, null, roadRow);
} catch (SQLException e) {
}
ContentValues linkRow = new ContentValues();
linkRow.put(Tables.StatesRoads.STATE_ID, roads.getState());
linkRow.put(Tables.StatesRoads.ROAD_ID, road.getId());
try {
db.insertOrThrow(Tables.STATES_ROADS, null, linkRow);
} catch (SQLException e) {
}
}
}
}
Mo Kargas is right. Your db helper should look more like this http://code.google.com/p/android-notes/source/browse/trunk/src/com/bitsetters/android/notes/DBHelper.java?r=10
This may fix your issue though Replace
SQLiteDatabase db = this.getWritableDatabase();
with this
SQLiteDatabase db;
try {
db = dbHelper.getWritableDatabase();
} catch (SQLiteException e) {
db = dbHelper.getReadableDatabase();
}
I've had huge issues in the past with not closing the database handle - generally I do all db operations inside an SQLiteOpenHelper subclass, keeping a reference to the db, opening and closing it atomically.
I defined a method to add an entry in my database helper class and insert data into database using it, but it is not working. This is my method defined in databasehelper class:
public void createchannelEntry(ChannelPoster channel) {
openDB();
ByteArrayOutputStream out = new ByteArrayOutputStream();
channel.getPoster().compress(Bitmap.CompressFormat.PNG, 100, out);
ContentValues cv = new ContentValues();
cv.put(KEY_POSTER, out.toByteArray());
cv.put(KEY_CHANNEL, channel.getChannel());
cv.put(KEY_PATH, channel.getPath());
cv.put(KEY_DBLINK, channel.getDBlink());
mDb.insert(channelS_TABLE, null, cv);
closeDB();
}
this is how I insert data
Bitmap sherlock = BitmapFactory.decodeResource(getResources(), R.drawable.sherlock);
mDB.createchannelEntry(new ChannelPoster(sherlock, "aa" ,"ll" ,"ha" ));
and I have a JavaBean for holding an entry
public class ChannelPoster {
private Bitmap poster;
private String channel;
private String path;
private String dblink;
public ChannelPoster(Bitmap pi, String c, String p, String d) {
poster = pi;
channel = c;
path = p;
dblink = d;
}
public Bitmap getPoster() { return poster; }
public String getChannel() { return channel; }
public String getPath() { return path; }
public String getDBlink() { return dblink; }
}
And because I am adding entries one by one, the program runs very slow, so it's there a faster way to insert many entries? like get all of them in one event?
I suggest not saving the images in the database, but rather saving them as files, and saving a path to them in the database (using a normal TEXT field).
If you don't want to do that, there are two things that will still greatly improve the speed of your processing:
Open and close the database only once around all your inserts
Use a transaction
Something like that:
SQLiteDatabase db = openDB();
db.beginTransaction();
try {
// Add here the loop with all your inserts
db.setTransactionSuccessful();
} finally {
db.endTransaction(); // will rollback and cancel the inserts if not marked as successful
db.close();
}
(the finally is there to make sure you close the transaction and the connection even if it somehow fails during the inserts: in this case ALL your inserts will be cancelled)