I want to store an image (size approx. 10MB) in the SQLite database. For that I created a DB helper, a Dao. Everything works fine, I can create several records and read them without a problem, I can even update the blob in the latest record without a problem.
But if I go back to an older record and update the blob, I cannot load this record with the blob any longer.
I have a list view where I show all the records, and for that I use a select that doesn't return the blob. This list works fine, but when I click on an item in the list, I try to load the record with the blob, the cursor returns 0 rows.
public void save(Bill aBill) {
ContentValues values = new ContentValues();
values.put(DatabaseHelper.BILL_NAME_COLUMN, aBill.getName());
values.put(DatabaseHelper.BILL_DUE_DATE_COLUMN, getContentValue(aBill.getDueDate()));
values.put(DatabaseHelper.BILL_IMAGE_COLUMN, aBill.getImage());
if (!aBill.isPersistent()) {
aBill.setId(database.insert(DatabaseHelper.BILL_TABLE, null, values));
aBill.setPersistent(true);
} else {
database.update(DatabaseHelper.BILL_TABLE, values, DatabaseHelper.BILL_ID_COLUMN + "=?", new String[]{String.valueOf(aBill.getId())});
}
}
// fails after updating the blob
public Bill get(long id) {
Cursor cursor = database.query(DatabaseHelper.BILL_TABLE,
new String[]{DatabaseHelper.BILL_ID_COLUMN, DatabaseHelper.BILL_NAME_COLUMN, DatabaseHelper.BILL_DUE_DATE_COLUMN, DatabaseHelper.BILL_IMAGE_COLUMN}, "id = ?", new String[] {String.valueOf(id)}, null,
null, DatabaseHelper.BILL_DUE_DATE_COLUMN);
Bill bill = null;
while (cursor.moveToNext()) {
bill = new Bill();
bill.setPersistent(true);
bill.setId(cursor.getLong(cursor.getColumnIndex(DatabaseHelper.BILL_ID_COLUMN)));
bill.setName(cursor.getString(cursor.getColumnIndex(DatabaseHelper.BILL_NAME_COLUMN)));
bill.setDueDate(getDate(cursor.getString(cursor.getColumnIndex(DatabaseHelper.BILL_DUE_DATE_COLUMN))));
bill.setImage(cursor.getBlob(cursor.getColumnIndex(DatabaseHelper.BILL_IMAGE_COLUMN)));
}
cursor.close();
return bill;
}
//works fine after updating the blob
public List findAll() {
List bills = new ArrayList();
Cursor cursor = database.query(DatabaseHelper.BILL_TABLE,
new String[]{DatabaseHelper.BILL_ID_COLUMN, DatabaseHelper.BILL_NAME_COLUMN, DatabaseHelper.BILL_DUE_DATE_COLUMN}, null, null, null,
null, DatabaseHelper.BILL_DUE_DATE_COLUMN);
while (cursor.moveToNext()) {
Bill bill = new Bill();
bill.setPersistent(true);
bill.setId(cursor.getLong(cursor.getColumnIndex(DatabaseHelper.BILL_ID_COLUMN)));
bill.setName(cursor.getString(cursor.getColumnIndex(DatabaseHelper.BILL_NAME_COLUMN)));
bill.setDueDate(getDate(cursor.getString(cursor.getColumnIndex(DatabaseHelper.BILL_DUE_DATE_COLUMN))));
bills.add(bill);
}
cursor.close();
return bills;
}
Here is the exception:
java.lang.IllegalStateException: Couldn't read row 0, col 0 from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it.
at android.database.CursorWindow.nativeGetLong(Native Method)
at android.database.CursorWindow.getLong(CursorWindow.java:511)
at android.database.AbstractWindowedCursor.getLong(AbstractWindowedCursor.java:75)
at net.rka.android.billreminder.BillDao.get(BillDao.java:106)
I suspect that updating a blob in a row corrupts the database somehow.
Did anybody run into a similar problem? If so how did you solve it?
Your issue is very likely due to the size of the image(s) and a quirk, for want of a better term, that you can store large BLOB's without issue, but due to the size limitations of an Android's Cursor Window of 2m, that you may not be able to retrieve the BLOB. This sometimes compounded by some of the SQLiteDatabase/Cursor (The Cursor getBlob() or it's underlying methods in this case) methods that basically hide underlying failures, in order to provide what is often a simpler development experience.
If you used the SQLiteDatabase DatabaseUtils.dumpCursor this may highlight the issue(s) that may have been hidden by the SQLiteDatabase query convenience method. So adding :-
DatabaseUtils.dumpCursor(cursor); //<<<< ADDED
while (cursor.moveToNext()) { ........
May provide clues.
I can think of 3 options :-
Rather than store the files as BLOBS, store files as files on disk and store the path in the Database.
Significantly reduce the size of the images.
Look into using C++ and the native SQLIte3 libraries to retrieve the BLOBS into a suitably sized container.
Perhaps the may be some libraries that do this. However, I don't recall any being mentioned.
Related
I am trying to insert data into a SQLite DB once a notification is received via FCM. For debugging purpose I am also inserting a dummy data into my DB when SHow Token is clicked on the HomeScreen activity.
However am getting
"I am getting "Couldn't read row 0, col -1 from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it."
Link to my code: - GitHub
Can someone please go through my code and let me know where I am going wrong.
Note - I added below in HomeScreen.java,MyFirebaseMessagingService.java and NotificationDetails.java
private SQLiteDB dbHelper = new SQLiteDB(this);
since the suggested
private SQLiteDB dbHelper;
did not work for me
When I used above I kept on getting Nullpointer exception, so I figured since the SQLiteDB class constructor is accepting a context, so let me pass one, post which I did not get NullPointer Exception.
Now I did this without being fully aware of the concept on context which I have been trying to wrap my head around, but since am an extreme noob to android I am not able to grasp it just yet. I suspect it might have something to do with the context I am passing.
Can someone please help me here with detailed instructions on how to fix this issue, I have been through many other threads on this but was not able to fix hence after 5 hrs of going through multiple SO questions, I am posting this one.
Thanks in advance to everyone in the community for the help. :)
Edit
Upon suggestion by admins, I am including below snippet of my code.
Where I am calling the cursor
dbHelper.insertNotification("This is a notification");
//Check if the message contains data
Cursor rs = dbHelper.getAllNotifications();
rs.moveToFirst();
token_text.setText("Token: " +rs.getString((rs.getColumnIndex("NOTIFICATION_DETAILS"))));
Insert Notification Function in SQLiteDB.java
public boolean insertNotification(String notification){
SQLiteDatabase db = this.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(NOTIFICATION_DETAILS,notification);
db.insert(NOTIFICATION_TABLE_NAME,null,contentValues);
return true;
}
getAllNotifications function
public Cursor getAllNotifications() {
SQLiteDatabase db = this.getWritableDatabase();
Cursor res = db.rawQuery( "SELECT * FROM " + NOTIFICATION_TABLE_NAME, null );
return res;
}
Couldn't read row 0, col -1 from CursorWindow.
Is saying that you are attempting to get the column at offset -1 from row 0 (the first row). So you have provided an invalid offset (it cannot be an offset of -1, the offset must be 0 or greater and the maximum value will be 1 less than the number of columns in the Cursor).
The most likely cause, is that Cursor method getColumnIndex(the_column_name_as_a_string) will return -1 when the column passed to the method cannot be found in the Cursor. Noting that due to a bug column name is case sensitive.
As such your issue is that the Cursor does not contain a column name NOTIFICATION_DETAILS and as you have used * (all columns) then that column does not exist in the table.
By the looks of it you should be using the String variable NOTIFICATION_DETAILS so you probably need to use :-
token_text.setText("Token: " +rs.getString((rs.getColumnIndex(NOTIFICATION_DETAILS)))); //<<<<<<<<<< double quotation marks removed.
Additional
You should NEVER assume that moveToFirst (or any Cursor move???? method) actually does the move. You should ALWAYS check the returned value. It will be true if the move was successful otherwise it would be false.
Again note that the column name passed to the getColumnIndex method is case dependant.
As such you should use something like
:-
dbHelper.insertNotification("This is a notification");
//Check if the message contains data
Cursor rs = dbHelper.getAllNotifications();
if (rs.moveToFirst()) {
token_text.setText("Token: " +rs.getString((rs.getColumnIndex(NOTIFICATION_DETAILS))));
} else {
........ code here if anything need to be done if there are no rows extracted
}
Addition re comment :-
Cursor rs = dbHelper.getAllNotifications(); rs.moveToFirst(); do{ for
(int i = 0; i < rs.getColumnCount(); i++) {
notification_array.add(rs.getString((rs.getColumnIndex(NOTIFICATION_DETAILS))));
} }while (rs.moveToNext());
using the following is much simpler :-
Cursor rs = dbHelper.getAllNotifications();
while (rs.moveToNext()) {
notification_array.add(rs.getString((rs.getColumnIndex(NOTIFICATION_DETAILS))));
}
In short:
Performing 23770 SELECT queries using query() and retrieving result using a Cursor takes 7 sec. I was able to reduce the time to 1 sec for the same by compiling the statement using compileStatement() and calling simpleQueryForString().
Is there a way to get similar performance without using compileStatement() since compileStatement() is limited to retrieving result only if output is 1x1 table?
More info:
I have an Android app which uses an SQLite database with a table having the following schema:
CREATE TABLE testtable(
id number primary key,
sentence text not null
);
The table is indexed on id.
What a part of my app does is to get an array of id's as input and retrieve the corresponding sentences from the table testtable.
I started by using the query() method which took around 7 sec to retrieve sentences for an array of 23770 ids. (23770 queries in 7 seconds)
I was trying to improve performance and I came to know that SQLiteStatement compileStatement(String sql) can improve performance by compiling the statements beforehand. And since SQLiteStatement has a method String simpleQueryForString() to retrieve results if the output is 1 x 1 table(which satisfies my usecase currently), I used it.
The improvement was massive. It could complete the same 23770 queries in 1 sec.
Although I can use this for now, the query may get complicated in future and the output may conatin more rows and columns which will make me use query() method.
So my question is: Is there a way to optimize queries without using compileStatement() and get similar performance?
This is the code I am testing with (The code using compileStatement() is commented):
public class DBMan extends SQLiteAssetHelper{
SQLiteDatabase db;
public DBMan(Context context){
super(context, "my.db", null, 1);
db = this.getReadableDatabase();
}
public String[] getSentences(Integer[] idList){
String[] result = new String[idList.length];
Cursor cur = null;
long timeStart = System.nanoTime();
try {
db.beginTransaction();
/* SQLiteStatement selStmt = db.compileStatement("SELECT sentence FROM testtable WHERE id=?"); */
for (int i = 0; i < idList.length; i++) {
// Querying using compileStatement() and simpleQueryForString()
/*
selStmt.clearBindings();
selStmt.bindLong(1, idList[i]);
result[i] = selStmt.simpleQueryForString();
*/
// Querying using query() and Cursor
cur = db.query(
"testtable",
new String[]{"sentence"},
"id = ?",
new String[]{String.valueOf(idList[i])},
null, null, null
);
if (cur.moveToFirst()) {
result[i] = cur.getString(0);
}
if (cur != null) {
cur.close();
}
}
db.setTransactionSuccessful();
}
finally {
db.endTransaction();
}
long totalTime = System.nanoTime() - timeStart;
Log.i("MYAPP", "DB total query time: "+totaltime/1000000000.0+" sec");
return result;
}
}
I'm using SQLiteAssetHelper which is an extension of SQLiteOpenHelper. I'm using it to copy my database file from assets folder on first run instead of creating it.
I'm used transactions although I'm doing only select queries as it reduces the number of shared locks that are obtained and dropped(see here).
This is a general performance question, I have no error or something like this.
I am working on an app which uses a SQLite Database, and just want to know what is the best, fastest and most efficient way to query through a table to find a special value.
Example:
I have a table and I am searching for a special string.
I get all rows by:
Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_NAME, null);
and then iterate through the cursor like
String searchedString = "THIS_IS_JUST_AN_EXAMPLE_PLEASE_IGNORE_TYPING_ERROR";
boolean success = false;
int count = cursor.getCount();
for(int i=0;i<count;i++) {
String queryString = c.getString(1);
if(queryString.equals(searchedString) {
success=true;
break;
} else {
cursor.moveToNext();
}
}
Another possible way would be to use query():
Cursor cursor = db.query(
TABLE_NAME, new String[] {STRING_COL},
STRING_NAME + "=?",
new String[] {"THIS_IS_JUST_AN_EXAMPLE_PLEASE_IGNORE_TYPING_ERROR"},
null, null, null, null);
Is there a performance difference between these two methods?
A lot of tutorials out there are showing the iteration via loop, but the Docs recommend to use the query() method.
Is the query() method the same as iterating through a loop?
How exactly does it work? I can't find in any API.
Doing the search by hand requires more code (which increases the risk of errors).
The DB probably executes a similar loop, but doing the search in the DB does not require all of the data be moved from the DB to your application.
If there is not much data, there will not be any noticeable performance difference between the two algorithms. However, when the amount of data becomes larger, you can speed up the SQL search by simply creating an index.
The only difference I can spot is the WHERE part, which lacks in the first algorithm.
The first algorithm will benefit a lot, if you add a WHERE clause to the query. And then become identical to the second algorithm, in terms of performances.
Something like
Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_NAME + " WHERE STRING_COL = ?", new String[]{"THIS_IS_JUST_AN_EXAMPLE_PLEASE_IGNORE_TYPING_ERROR"});
As it is right now, the first algorithm is slower.
As noted bt #CL, both the algorithms can be drammatically improved by indexing the WHERE column.
I am fairly new to Android and trying to learn how things work module by module. Here's what I am trying to do:
Show a word with a favorite checkbox (image). If a user taps on it then the database is updated and a column in database table stores its value (1 for checked, 0 for unchecked). I am using a cursor to retrieve values of both the word and favorite checkbox. Tapping on the favorite image correctly updates the database without any problem.
The problem I am facing is:
Unless I exit the application and start it again, the cursor doesn't fetch the recent changes made to the database. To explain it further, when I navigate to the next/previous word (using a button at the bottom of screen) the values retrieved aren't the latest ones i.e it seems like the cursor still has the old database values and not the updated ones.
I did search through Google, StackOverflow to get a concrete solution but it seems like I am not using the right search terms. I know this has something to do with updating cursor and the fact that requery is depreciated but again I have lost direction.
[EDIT] Using the below mentioned method to get Cursor:
public Cursor getWords() {
SQLiteDatabase db = getReadableDatabase();
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
String[] sqlSelect = {"_id", "word", "favourite"};
String sqlTables = "word_list";
qb.setTables(sqlTables);
Cursor c = qb.query(db, sqlSelect, null, null,
null, null, null);
c.moveToFirst();
return c;
}
This method is called when user taps on favorite image to update the database:
public void setFavWord(int markFav, int wordPos) {
SQLiteDatabase db = getWritableDatabase();
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
ContentValues values = new ContentValues();
values.put("favourite", markFav);
db.update("word_list", values, "_id = " + wordPos, null);
}
A cursor is not dynamic; it shows a snapshot of the database at the time the query was executed.
When the database changes, you must execute the query again.
As suggested by #Sreekanth in comments section, I am updating the cursor whenever favorite image is tapped. Although it is working just fine but I think it as a workaround rather than a solution. Maybe I am wrong in saying so.
This question should be a starting point to all of us who want to manipulate contacts in Android.
First things first
As I am aware, since API level 5 the Contacts API has changed, so in order to make the application work correct I need to check what android os is on the phone and if prior 5 use one content provider or else use the newer one. The only annoyance in this case is the warnings of deprecated I get. The application is build against Android 2.3.3 but needs to work from 1.5+
1. Querying contacts
This is the easiest part to do. Usually querying means getting data like Contact name, phones, picture, email and displaying it on a listview. For instance here is how I've done it in API prior 5
String[] projectionPeople = new String[] {People._ID, People.NAME,};
String[] projectionPhone = new String[] {Phones.NUMBER};
try {
// Get the base URI for People table in Contacts content provider.
// which is: content://contacts/people/
Uri contactUri = People.CONTENT_URI;
ContentResolver resolver = getContentResolver();
Cursor phonesCursor = null;
Cursor peopleCursor = resolver.query (contactUri,
projectionPeople, //Which columns to return.
"People.NAME is not null", // WHERE clause--we won't specify.
null, // Selection Args??
People.DEFAULT_SORT_ORDER); // Order-by name
if (peopleCursor != null && peopleCursor.getCount() >0)
{
// go to the beginning of the list
peopleCursor.moveToFirst();
do
{
//do something with current contact info
phoneUri= Uri.withAppendedPath(personUri, Contacts.People.Phones.CONTENT_DIRECTORY);
phonesCursor = resolver.query(phoneUri,
projectionPhone,
null,
null,
Phones.DEFAULT_SORT_ORDER);
if (phonesCursor!=null && phonesCursor.getCount()>0)
{
phonesCursor.moveToFirst();
lstPhones = new ArrayList<String>();
do
{
//add phone numbers to a List<String> for instance
} while (phonesCursor.moveToNext());
if (phonesCursor != null && !phonesCursor.isClosed())
phonesCursor.close();
} while (peopleCursor.moveToNext());
if (peopleCursor != null && !peopleCursor.isClosed())
peopleCursor.close();
}
}
catch (Exception ex)
{
}
}
Haven't tried it yet on the new api but the cursor should be like
final String[] projection = new String[] {
RawContacts.CONTACT_ID, // the contact id column
RawContacts.DELETED // column if this contact is deleted
};
final Cursor rawContacts = managedQuery(RawContacts.CONTENT_URI, // the URI for raw contact provider
projection
null, // selection = null, retrieve all entries
null, // selection is without parameters
null); // do not order
Sure, this needs to be elaborated a bit more, but it should provide the basics of simple query against Contacts content provider
2. Backup
My first thought on this was: if I know the Id of a Contact, I create tables in a sqlite database exactly how the cursor columns are and insert all the data into my tables. This is not an easy task as it requires a lot of codding not to mention that different apis have different table structures. What would be the best solution to backup one contact or multiple contacts ?
3. Delete
This should work on all apis using content providers, but data is spread on many packages and uris and I'm not sure from where to delete
4. Insert
After a contact is backed up, I may need to restore/insert it again. As in case of deletion, on which uris do I need to insert ?
Please, let's try to elaborate this issues so in the futures, who needs to use Contacts in Android apps could take this question as a solid starting point. Thank you stackoverflow community.
Here is a good starting point
http://developer.android.com/resources/samples/BusinessCard/index.html