My goal
I want to insert multiple records into sqlite in batches (transactionally).
My question
I found the method android.content.ContentResolver.bulkInsert(..) interesting but the javadoc states:
This function make no guarantees about the atomicity of the insertions.
Why would android provide a method that is crippled ? Can you name me usecases for non-atomic insertions ? I am going to obviously going to override ContentProvider.bulkInsert(..) to ensure atomicity myself so I'm not sure why it is phrase like this.
We need to override the bulk insert method like following...
public class Provider extends ContentProvider {
public static final Uri URI = Uri.parse("content://com.example.android.hoge/");
#Override
public String getType(Uri uri) {
return null;
}
#Override
public boolean onCreate() {
return false;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Helper helper = Helper.getInstance(getContext(), null);
SQLiteDatabase sdb = helper.getReadableDatabase();
Cursor cursor = sdb.query(
Table.TABLENAME,
new String[]{Table.ID, Table.DATA, Table.CREATED},
selection,
selectionArgs,
null,
null,
sortOrder,
null
);
return cursor;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
Helper helper = Helper.getInstance(getContext(), null);
SQLiteDatabase sdb = helper.getWritableDatabase();
sdb.insert(Table.TABLENAME, null, values);
getContext().getContentResolver().notifyChange(uri, null);
return uri;
}
/**
* super.bulkInsert is implemented the loop of insert without transaction
* So we need to override it and implement transaction.
*/
#Override
public int bulkInsert(Uri uri, ContentValues[] values) {
Helper helper = Helper.getInstance(getContext(), null);
SQLiteDatabase sdb = helper.getWritableDatabase();
sdb.beginTransaction();
SQLiteStatement stmt = sdb.compileStatement(
"INSERT INTO `" + Table.TABLENAME + "`(`" + Table.DATA + "`, `" + Table.CREATED + "`) VALUES (?, ?);"
);
int length = values.length;
for(int i = 0; i < length; i++){
stmt.bindString(1, values[i].getAsString(Table.DATA));
stmt.bindLong(2, values[i].getAsLong(Table.CREATED));
stmt.executeInsert();
}
sdb.setTransactionSuccessful();
sdb.endTransaction();
return length;
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
Helper helper = Helper.getInstance(getContext(), null);
SQLiteDatabase sdb = helper.getWritableDatabase();
int rows = sdb.update(Table.TABLENAME, values, selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null);
return rows;
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
Helper helper = Helper.getInstance(getContext(), null);
SQLiteDatabase sdb = helper.getWritableDatabase();
int rows = sdb.delete(Table.TABLENAME, selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null);
return rows;
}
private static class Helper extends SQLiteOpenHelper {
static Helper INSTANCE = null;
private Helper(Context context, CursorFactory factory) {
super(context, Table.FILENAME, factory, Table.VERSION);
}
public static Helper getInstance(Context context, CursorFactory factory) {
if (INSTANCE == null) {
INSTANCE = new Helper(context, factory);
}
return INSTANCE;
}
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(
"CREATE TABLE `" + Table.TABLENAME + "`(" +
" `" + Table.ID + "` INTEGER PRIMARY KEY AUTOINCREMENT," +
" `" + Table.CREATED + "` INTEGER," +
" `" + Table.DATA + "` TEXT" +
");"
);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
}
Use applyBatch() instead.
This allows you to perform many different operations in a transactional manner, however there is a performance hit for this fexibility.
The relevant documentation can be found here in the ContentResolver SDK documentation
I have provided a quick tutorial on using applybatch in the symantics of backReferences
I also recommend looking at this question which discusses overriding applyBatch
This function make no guarantees about the atomicity of the
insertions.
Correct me if I'm wrong but this is because we have no idea whether the given content provider overrides the bulkInsert() method unless it is our own provider. If the bulkInsert() method is not overriden, default implementation will iterate over the values and call insert(Uri, ContentValues) on each of them. It should be fine if you are using your own provider and know that you have implemented the bulkInsert() method like following example and use the endTransaction() method in finally block:
#Override
public int bulkInsert(Uri uri, ContentValues[] values) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
switch (match) {
case WEATHER:
db.beginTransaction();
int returnCount = 0;
try {
for (ContentValues value : values) {
normalizeDate(value);
long _id = db.insert(WeatherEntry.TABLE_NAME,
null, value);
if (_id != -1) {
returnCount++;
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
getContext().getContentResolver().notifyChange(uri, null);
return returnCount;
default:
return super.bulkInsert(uri, values);
}
}
First Add method for bulk insert in your content provider
#Override
public int bulkInsert(#NonNull Uri uri, #NonNull ContentValues[] values) {
switch (uriMatcher.match(uri)) {
case USERS:
for (ContentValues value : values) {
long rowID = sqLiteDatabase.insert(YOUR_TABLENAME, "", value);
if (rowID > 0) {
Uri _uri = ContentUris.withAppendedId(CONTENT_URI, rowID); //append ID into CONTENT_URI
getContext().getContentResolver().notifyChange(_uri, null);
return values.length; //return total number of data inserted
}
}
break;
}
return super.bulkInsert(uri, values);
}
Add below code in button click (Below is executed to insert bulk data)
String userName = editTextUserName.getText().toString();
String userCity = editTextUserCity.getText().toString();
Log.d("BulkInsert", "onClick: -------START------");
ContentValues[] contentValue = new ContentValues[5000];
for (int i = 0; i < 5000; i++) {
contentValue[i] = new ContentValues(); // initialize Array of content values
//store data in content values object
contentValue[i].put(UserModel.USER_CITY, userCity);
contentValue[i].put(UserModel.USER_NAME, userName);
contentValue[i].put(UserModel.USER_PINCODE, userPincode);
}
int count = getContentResolver().bulkInsert(YOUR_TABLE_URI, contentValue); //insert data
Log.d("BulkInsert", "onClick: " + count); //Display number of data inserted
Log.d("BulkInsert", "onClick: -------STOP------");
Related
i had been trying to prevent accidentally click on delete button when the data base is empty. It will crash after click.
Database handler
public void deleteLastMessage(Class a) {
SQLiteDatabase db = this.getWritableDatabase();
db.delete(TABLE_NAME, KEY_MSG + " = ?",
new String[] { String.valueOf(a.get_message()) });
db.close();
}
public String getLastString() {
String selectQuery = "SELECT * FROM " + TABLE_NAME;
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
cursor.moveToLast();
LastString = cursor.getString(0);
cursor.close();
db.close();
return LastString;
}
Activity
public void deleteMessage(View v) {
LastMessage = new SubliminalClass(db.getLastString());
db.deleteLastMessage(LastMessage);
It work fine when there are data to delete, it crashed when there is no data.
My data is a column of string.
Referred to this Application crashes while reading an empty table in android but to no avail.
I have tried this below but still crashed when there is no data.
public boolean checkdb(){
SQLiteDatabase db = this.getReadableDatabase();
Cursor mCursor = db.rawQuery("SELECT * FROM " + TABLE_NAME, null);
Boolean rowExists;
String nullString="";
if (nullString.equals(getLastString())) //todo change this
// DO SOMETHING WITH CURSOR
rowExists = false;
else
{
// I AM EMPTY
rowExists = true;
}
return rowExists;
}
Anyone can help me solve this?
Try this
Check the table row count is greater than zero then do the delete operation.
//Add in your activity
int rowCount = db.getRowCount();
db.close();
if(rowCount>0)
{
db.deleteLastMessage(LastMessage);
}else{
}
//Add in DBhelperClass
public int getRowCount() {
String countQuery = "SELECT * FROM " + TABLE_NAME;
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery(countQuery, null);
int cnt = cursor.getCount();
cursor.close();
return cnt;
}
public void deleteLastMessage(Class a) {
try{
SQLiteDatabase db = this.getWritableDatabase();
db.delete(TABLE_NAME, KEY_MSG + " = ?",
new String[] { String.valueOf(a.get_message()) });
db.close();
} catch (Exception e){
e.printStackTrace();
}
}
You could add try/catch around your delete code.
Also the checkdb function could be like this
public boolean checkdb(){
SQLiteDatabase db = this.getReadableDatabase();
Log.d(TAG,"Got Readable DB")
Cursor mCursor = db.rawQuery("SELECT * FROM " + TABLE_NAME, null);
Boolean rowExists = false;
String nullString="";
if(mCursor != null){
Log.d(TAG,"Cursor is not null")
try{
rowExists = mCursor.getCount() > 0;
Log.d(TAG,"rowExists is " + rowExists);
mCursor.close();
} catch (Exception e){
e.printStackTrace();
}
}
return rowExists;
}
I am populating contact list details to list view successfully.
My code:
String order = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC";
Cursor curLog = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,null,null,order);
How can I avoid the duplicate data In List view as the contact details is repeating if its joined contact i.e. joined with both phone and Google?. The screen is like
I want to select programmatically only 1 name not the both? Any Idea how I can select?
I have used a rough way to avoid this problem which helped me so much and working nicely.
i.e
Use local database (SQLite) to avoid duplicate data by make phone number to unique.
I have made one SQLite DB to handle this problem:
ContactMerger.java:
public class ContactMerger {
private static final String CONTACT_TABLE = "_contact_table";
private static final String CONTACT_ID = "_contactId";
private static final String CONTACT_NAME = "_contactName";
private static final String CONTACT_MOBILE_NUMBER = "_contactNumber";
private static final String CONTACT_DATE = "_contactDate";
private static final int DATABASE_VERSION = 1;
private static final String DATABASE_NAME = "DB_Contact";
private final Context context;
private SQLiteDatabase ourDatabase;
private DbHelper ourHelper;
private class DbHelper extends SQLiteOpenHelper {
public DbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
#Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
String contactQuery = "CREATE TABLE " + CONTACT_TABLE + " ("
+ CONTACT_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ CONTACT_NAME + " TEXT NOT NULL, " + CONTACT_DATE
+ " TEXT NOT NULL, " + CONTACT_MOBILE_NUMBER
+ " TEXT NOT NULL UNIQUE);";
db.execSQL(contactQuery);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
db.execSQL("DROP TABLE IF EXISTS " + CONTACT_TABLE);
onCreate(db);
}
}
public ContactMerger(Context context) {
this.context = context;
}
public ContactMerger open() throws SQLException {
ourHelper = new DbHelper(context);
ourDatabase = ourHelper.getWritableDatabase();
return this;
}
public void close() {
ourHelper.close();
}
// Insert Data to Contact Table
public long insertContacts(String name, String number, String date) throws SQLException {
ContentValues cv = new ContentValues();
cv.put(CONTACT_NAME, name);
cv.put(CONTACT_DATE, date);
cv.put(CONTACT_MOBILE_NUMBER, number);
Log.d("Insert Data", cv.toString());
return ourDatabase.insert(CONTACT_TABLE, null, cv);
}
//Get Contact details from Contact Table
public ArrayList<ContactHolder> getContactDetails() throws Exception{
ArrayList<ContactHolder> contactDetails = new ArrayList<ContactHolder>();
String[] columns = new String[] { CONTACT_ID, CONTACT_NAME, CONTACT_DATE, CONTACT_MOBILE_NUMBER };
Cursor c = ourDatabase.query(CONTACT_TABLE, columns, null, null, null,null, null);
int iContactName = c.getColumnIndex(CONTACT_NAME);
int iContactDate = c.getColumnIndex(CONTACT_DATE);
int iContactMobileNumber = c.getColumnIndex(CONTACT_MOBILE_NUMBER);
for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) {
ContactHolder data = new ContactHolder();
data.setName(c.getString(iContactName));
data.setDate(c.getString(iContactDate));
data.setNumber(c.getString(iContactMobileNumber));
contactDetails.add(data);
}
return contactDetails;
}
}
Here ContactHolder is just a getter/setter class to handle contact entities.
First I inserted all Contact information once in my MainActivity by the help of a background thread. It prevents to insert the contact info multiple times.
Something like:
private ArrayList<ContactHolder> contactHolder;
private void setCallLogs(Cursor managedCursor) {
contactHolder = new ArrayList<ContactHolder>();
int _number = managedCursor
.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
int _name = managedCursor
.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
int _id = managedCursor
.getColumnIndex(ContactsContract.CommonDataKinds.Phone._ID);
while (managedCursor.moveToNext()) {
ContactHolder holder = new ContactHolder();
holder.setNumber(managedCursor.getString(_number));
holder.setName(managedCursor.getString(_name));
holder.setDate(managedCursor.getString(_id));
contactHolder.add(holder);
}
Thread t = new Thread(new Runnable() {
#Override
public void run() {
for(int i=0; i<contactHolder.size(); i++){
try{
ContactMerger merger = new ContactMerger(HomeActivity.this);
merger.open();
merger.insertContacts(contactHolder.get(i).getName(),
contactHolder.get(i).getNumber(),
contactHolder.get(i).getdate());
merger.close();
} catch(Exception e){
e.printStackTrace();
}
}
}
});
t.start();
}
At last I gtt all contact information inside an Asynctask(doInbackground()) and put in adapter/listview in its onPostExecute() method in the class I want to show.
Here:
#Override
protected ArrayList<ContactHolder> doInBackground(String... parameters) {
ArrayList<ContactHolder> filterContacts = new ArrayList<ContactHolder>();
ContactMerger merger = new ContactMerger(Aaja_Contact.this);
merger.open();
try {
filterContacts = merger.getContactDetails();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
merger.close();
return filterContacts;
}
I believe this may happen if the contact number is stored in two different ways/formats: for example in your case the number for Akshay may be saved as 982-0123456 and 9820123456
Did you try displaying the number along with the Name by including the Number as well in the list view?
You need to retrieve the data from the Cursor to HashSet (which don't allows duplicate itmes) and then pass the HashSet object to your ListView's Adapter
This is a dump solution but it will help you:
ListView listView;
Set<String> listItems;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.listView);
listItems = new HashSet<String>();
String order = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC";
Cursor curLog = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,null,null,order);
if(curLog != null) {
while(curLog.moveToNext()) {
String str = curLog.getString(curLog.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME_PRIMARY));
listItems.add(str);
}
}
String listString = listItems.toString();
listString = listString.substring(1,listString.length()-1);
String[] newList = listString.split(", ");
ArrayAdapter<String> adapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, newList);
listView.setAdapter(adapter);
}
Good luck..
Since you're querying Phone.CONTENT_URI, I'm assuming you're looking for contacts with phone number.. then you can use ContactsContract.Contacts.CONTENT_URI
String order = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC";
Cursor curLog = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null,
ContactsContract.Contacts.HAS_PHONE_NUMBER + "=?", new String[] { "1" }, order);
Its because the listview is showing both normal contacts as well as whatsapp( or like this) linked contacts. Best is to store all the contacts in a Database and then retrieve the contacts using "select distinct..." command of SQL.
String order = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC";
Cursor phones = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,null,null, order);
String temp_name="";
while (phones.moveToNext())
{
String name=phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
if (name.equals(temp_name))
continue;
temp_name=name;
//add name to your list or adapter here`enter code here`
}
phones.close();
When you loop through your contacts, here's something you can do in the looping statement while you add your next object to avoid creating a duplicate contact:
UserList object=new UserList(name,number);
if(arrayList.size()==0)
{
arrayList.add(object);
}
if(arrayList.size()>0) {
position = arrayList.size();
if (!(arrayList.get(arrayList.position - 1).getName().equals(number) ||
arrayList.get(position - 1).getNumber().equals(number)))
{
arrayList.add(object); }
}
Here, in my object of 'UserList' class, the name and number would repeat from the contact list, so this code just checks if the previous object has the same name or number before adding in the new one.
Old question but still relevant. I could not find suitable query to skip dupes with contentresolver but it's possible to compare all contacts for duplicates by phone number.
With com.googlecode.libphonenumber library it's really simple. Method public MatchType isNumberMatch(CharSequence firstNumber, CharSequence secondNumber) compares number, coutry code, mask and return one of MatchType enum value.
I am creating task manager. I have tasklist and I want when I click on particular tasklist name if it empty then it goes on Add Task activity but if it has 2 or 3 tasks then it shows me those tasks into it in list form.
I am trying to get count in list. my database query is like:
public Cursor getTaskCount(long tasklist_Id) {
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor= db.rawQuery("SELECT COUNT (*) FROM " + TABLE_TODOTASK + " WHERE " + KEY_TASK_TASKLISTID + "=?",
new String[] { String.valueOf(tasklist_Id) });
if(cursor!=null && cursor.getCount()!=0)
cursor.moveToNext();
return cursor;
}
In My activity:
list_tasklistname.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0,
android.view.View v, int position, long id) {
db = new TodoTask_Database(getApplicationContext());
Cursor c = db.getTaskCount(id);
System.out.println(c.getCount());
if(c.getCount()>0) {
System.out.println(c);
Intent taskListID = new Intent(getApplicationContext(), AddTask_List.class);
task = adapter.getItem(position);
int taskList_id = task.getTaskListId();
taskListID.putExtra("TaskList_ID", taskList_id);
startActivity(taskListID);
}
else {
Intent addTask = new Intent(getApplicationContext(), Add_Task.class);
startActivity(addTask);
}
}
});
db.close();
}
but when I am clicking on tasklist name it is returning 1, bot number of tasks into it.
Using DatabaseUtils.queryNumEntries():
public long getProfilesCount() {
SQLiteDatabase db = this.getReadableDatabase();
long count = DatabaseUtils.queryNumEntries(db, TABLE_NAME);
db.close();
return count;
}
or (more inefficiently)
public int getProfilesCount() {
String countQuery = "SELECT * FROM " + TABLE_NAME;
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery(countQuery, null);
int count = cursor.getCount();
cursor.close();
return count;
}
In Activity:
int profile_counts = db.getProfilesCount();
db.close();
Use android.database.DatabaseUtils to get number of count.
public long getTaskCount(long tasklist_Id) {
return DatabaseUtils.queryNumEntries(readableDatabase, TABLE_NAME);
}
It is easy utility that has multiple wrapper methods to achieve database operations.
c.getCount() returns 1 because the cursor contains a single row (the one with the real COUNT(*)). The count you need is the int value of first row in cursor.
public int getTaskCount(long tasklist_Id) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor= db.rawQuery(
"SELECT COUNT (*) FROM " + TABLE_TODOTASK + " WHERE " + KEY_TASK_TASKLISTID + "=?",
new String[] { String.valueOf(tasklist_Id) }
);
int count = 0;
if(null != cursor)
if(cursor.getCount() > 0){
cursor.moveToFirst();
count = cursor.getInt(0);
}
cursor.close();
}
db.close();
return count;
}
I know it is been answered long time ago, but i would like to share this also:
This code works very well:
SQLiteDatabase db = this.getReadableDatabase();
long taskCount = DatabaseUtils.queryNumEntries(db, TABLE_TODOTASK);
BUT what if i dont want to count all rows and i have a condition to apply?
DatabaseUtils have another function for this: DatabaseUtils.longForQuery
long taskCount = DatabaseUtils.longForQuery(db, "SELECT COUNT (*) FROM " + TABLE_TODOTASK + " WHERE " + KEY_TASK_TASKLISTID + "=?",
new String[] { String.valueOf(tasklist_Id) });
The longForQuery documentation says:
Utility method to run the query on the db and return the value in the first column of the first row.
public static long longForQuery(SQLiteDatabase db, String query, String[] selectionArgs)
It is performance friendly and save you some time and boilerplate code
Hope this will help somebody someday :)
Change your getTaskCount Method to this:
public int getTaskCount(long tasklist_id){
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor= db.rawQuery("SELECT COUNT (*) FROM " + TABLE_TODOTASK + " WHERE " + KEY_TASK_TASKLISTID + "=?", new String[] { String.valueOf(tasklist_id) });
cursor.moveToFirst();
int count= cursor.getInt(0);
cursor.close();
return count;
}
Then, update the click handler accordingly:
public void onItemClick(AdapterView<?> arg0, android.view.View v, int position, long id) {
db = new TodoTask_Database(getApplicationContext());
// Get task list id
int tasklistid = adapter.getItem(position).getTaskListId();
if(db.getTaskCount(tasklistid) > 0) {
System.out.println(c);
Intent taskListID = new Intent(getApplicationContext(), AddTask_List.class);
taskListID.putExtra("TaskList_ID", tasklistid);
startActivity(taskListID);
} else {
Intent addTask = new Intent(getApplicationContext(), Add_Task.class);
startActivity(addTask);
}
}
In order to query a table for the number of rows in that table, you want your query to be as efficient as possible. Reference.
Use something like this:
/**
* Query the Number of Entries in a Sqlite Table
* */
public long QueryNumEntries()
{
SQLiteDatabase db = this.getReadableDatabase();
return DatabaseUtils.queryNumEntries(db, "table_name");
}
Do you see what the DatabaseUtils.queryNumEntries() does? It's awful!
I use this.
public int getRowNumberByArgs(Object... args) {
String where = compileWhere(args);
String raw = String.format("SELECT count(*) FROM %s WHERE %s;", TABLE_NAME, where);
Cursor c = getWriteableDatabase().rawQuery(raw, null);
try {
return (c.moveToFirst()) ? c.getInt(0) : 0;
} finally {
c.close();
}
}
Sooo simple to get row count:
cursor = dbObj.rawQuery("select count(*) from TABLE where COLUMN_NAME = '1' ", null);
cursor.moveToFirst();
String count = cursor.getString(cursor.getColumnIndex(cursor.getColumnName(0)));
looking at the sources of DatabaseUtils we can see that queryNumEntries uses a select count(*)... query.
public static long queryNumEntries(SQLiteDatabase db, String table, String selection,
String[] selectionArgs) {
String s = (!TextUtils.isEmpty(selection)) ? " where " + selection : "";
return longForQuery(db, "select count(*) from " + table + s,
selectionArgs);
}
Once you get the cursor you can do
Cursor.getCount()
I am using the query functions in order to build the SQL queries for my tables. Is there a way to see the actual query that is run? For instance log it somewhere?
So far the best I could do was to have a look at the cursor's member mQuery using a breakpoint. I'd love to output the queries automatically though. This member is of course not public and does not have a getter.
Just for the record, here is an implementation of the accepted answer.
/**
* Implement the cursor factory in order to log the queries before returning
* the cursor
*
* #author Vincent # MarvinLabs
*/
public class SQLiteCursorFactory implements CursorFactory {
private boolean debugQueries = false;
public SQLiteCursorFactory() {
this.debugQueries = false;
}
public SQLiteCursorFactory(boolean debugQueries) {
this.debugQueries = debugQueries;
}
#Override
public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
String editTable, SQLiteQuery query) {
if (debugQueries) {
Log.d("SQL", query.toString());
}
return new SQLiteCursor(db, masterQuery, editTable, query);
}
}
adb shell setprop log.tag.SQLiteStatements VERBOSE
Don't forget to restart your app after setting this property.
It is also possible to enable logging of execution time. More details are availabe here: http://androidxref.com/4.2.2_r1/xref/frameworks/base/core/java/android/database/sqlite/SQLiteDebug.java
You can apply your own SQLiteDatabase.CursorFactory to the database. (See the openDatabase parameters.) This will allow you to create your own subclass of Cursor, which keeps the query in an easily accessible field.
edit: In fact, you may not even have to subclass Cursor. Just have your factory's newCursor() method return a standard SQLiteCursor, but log the query before doing so.
adb shell setprop log.tag.SQLiteLog V
adb shell setprop log.tag.SQLiteStatements V
adb shell stop
adb shell start
Using an SQLiteQueryBuilder it's painfully simple. buildQuery() returns a raw sql string, which can then be logged:
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(ExampleTable.TABLE_NAME);
String sql = qb.buildQuery(projection, selection, null, null, sortOrder, null);
Log.d("Example", sql);
So far the best I could do was to have a look at the cursor's member mQuery using a breakpoint. This member is of course not public and does not have a getter, hence, no way to output it. Any better suggestion?
If you are using SQLiteDatabase with it's standard methods as insert, update and delete custom CursorFactory will not be working.
I implemented my not very great but working solution based on SQLiteDatabase class. It just repeats logic of insert, update and delete methods but without statements and actually doing the logging of SQL statements.
public class SQLiteStatementsLogger {
private static final String TAG = SQLiteStatementsLogger.class.getSimpleName();
private static final String[] CONFLICT_VALUES = new String[]
{"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "};
public void logInsert(String table, String nullColumnHack, ContentValues values) {
logInsertWithOnConflict(table, nullColumnHack, values, 0);
}
public static void logInsertWithOnConflict(String table, String nullColumnHack,
ContentValues initialValues, int conflictAlgorithm) {
StringBuilder sql = new StringBuilder();
sql.append("INSERT");
sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(" INTO ");
sql.append(table);
sql.append('(');
Object[] bindArgs = null;
int size = (initialValues != null && initialValues.size() > 0)
? initialValues.size() : 0;
if (size > 0) {
bindArgs = new Object[size];
int i = 0;
for (String colName : initialValues.keySet()) {
sql.append((i > 0) ? "," : "");
sql.append(colName);
bindArgs[i++] = initialValues.get(colName);
}
sql.append(')');
sql.append(" VALUES (");
for (i = 0; i < size; i++) {
sql.append((i > 0) ? ",?" : "?");
}
} else {
sql.append(nullColumnHack + ") VALUES (NULL");
}
sql.append(')');
sql.append(". (");
for (Object arg : bindArgs) {
sql.append(String.valueOf(arg)).append(",");
}
sql.deleteCharAt(sql.length()-1).append(')');
Log.d(TAG, sql.toString());
}
public static void logUpdate(String table, ContentValues values, String whereClause, String[] whereArgs) {
logUpdateWithOnConflict(table, values, whereClause, whereArgs, 0);
}
public static void logUpdateWithOnConflict(String table, ContentValues values,
String whereClause, String[] whereArgs, int conflictAlgorithm) {
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");
sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(table);
sql.append(" SET ");
// move all bind args to one array
int setValuesSize = values.size();
int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length);
Object[] bindArgs = new Object[bindArgsSize];
int i = 0;
for (String colName : values.keySet()) {
sql.append((i > 0) ? "," : "");
sql.append(colName);
bindArgs[i++] = values.get(colName);
sql.append("=?");
}
if (whereArgs != null) {
for (i = setValuesSize; i < bindArgsSize; i++) {
bindArgs[i] = whereArgs[i - setValuesSize];
}
}
if (!TextUtils.isEmpty(whereClause)) {
sql.append(" WHERE ");
sql.append(whereClause);
}
sql.append(". (");
for (Object arg : bindArgs) {
sql.append(String.valueOf(arg)).append(",");
}
sql.deleteCharAt(sql.length()-1).append(')');
Log.d(TAG, sql.toString());
}
public static void logDelete(String table, String whereClause, String[] whereArgs) {
StringBuilder sql = new StringBuilder("DELETE FROM " + table);
if (!TextUtils.isEmpty(whereClause)) {
sql.append(" WHERE " + whereClause);
sql.append(". (");
for (Object arg : whereArgs) {
sql.append(String.valueOf(arg)).append(",");
}
sql.deleteCharAt(sql.length()-1).append(')');
}
Log.d(TAG, sql.toString());
}
}
Be aware not to use the logger in release versions. It might increase time of queries executing.
You can check if the build is in debug mode with this code line:
0 != (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE)
If it is for once off scenario, I would suggest injecting an error (e.g. type in expression like LIEK instead of LIKE!) and watch the Eclipse LogCat for any errors! HTH!
If you are using a ContentProvider to access the DB, this is how I got it logging the queries. Not a perfect solution, but it works for development
#Override
public boolean onCreate() {
dbHelper = new MySQLiteHelper(getContext());
database=dbHelper.getWritableDatabase();
if(!database.isReadOnly())
database.execSQL("PRAGMA foreign_keys=ON;");
return true;
}
SQLiteDatabase.CursorFactory cursorFactory = new SQLiteDatabase.CursorFactory() {
#Override
public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, String editTable, SQLiteQuery query) {
Log.d(TAG, "Query: "+query);
return new SQLiteCursor(db, masterQuery, editTable, query);
}
};
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
String table =getTableName(uri);
if(Constants.LOG_QUERIES){
database = SQLiteDatabase.openOrCreateDatabase(database.getPath(), cursorFactory);
}
Cursor cursor =database.query(table, projection, selection, selectionArgs, null, null, sortOrder);
cursor.moveToFirst();
return cursor;
}
It'll throw a DatabaseNotClosed exception, but you'll be able to see the query
Personnally I log text using java.util.Log and the Log.w("MYAPPNAME", "My text...") function. It shows up in the Log view of Eclipse and it can be filtered to output only the logs for "MYAPPNAME".
I've spent a lot of hours searching and reading similar posts, but none of them seem to truly reflect my problem and thus I haven't found anything that works for me.
I've got a database, on which I perform a query, the results of which are stored in a cursor. There's two things to that:
-the query is performed everytime a certain button is pressed (thus the query is inside the OnClickListener for that Button)
-the query returns two different columns with String values, which must be treated separately (one column stores the names which must be shown in the ListView, the other stores the paths to the image associated toa row)
My problem is, I try to create a String[] which I need to pass to the ArrayAdapter creator for the ListView, but trying to assign it a size of Cursor getCount() crashes my activity. I hope the code will be more of an explanation:
OnClickListener searchListener = new OnClickListener() {
public void onClick(View v) {
CardDatabaseOpenHelper helper = new
CardDatabaseOpenHelper(DeckEditorScreen1.this);
SQLiteDatabase db = helper.getReadableDatabase();
String columns[] = {"name","number","path"};
Cursor c = db.query("cards", columns, null, null, null, null, "number");
int count = c.getCount();
String[] resultNameStrings;
if (count != 0) resultNameStrings = new String[count];
else {resultNameStrings = new String[1]; resultNameStrings[1] = "No results";}
// This is the offending code
//Note that if I assign fixed values to resutNameStrings, the code works just
//fine
for (int i = 0; i < count; ++i) {
c.moveToNext();
int col = c.getColumnIndex("name");
String s = c.getString(col);
//Ideally here I would to something like:
//resultNameStrings[i] = s;
col = c.getColumnIndex("number");
int conv = c.getInt(col);
col = c.getColumnIndex("path");
String s2 = c.getString(col);
}
db.close();
ArrayAdapter<?> searchResultItemAdapter = new ArrayAdapter<String>
(DBScreen.this,
R.layout.search_result_item,
resultNameStrings);
ListView searchResultList = (ListView)
DBScreen.this.findViewById(R.id.search_result_list);
searchResultList.setAdapter(searchResultItemAdapter);
}
};
Button search_button = (Button) findViewById(R.id.search_button);
search_button.setOnClickListener(searchListener);
EDITED twice :)
do it in "Android Way" ...
first use CursorAdapter (fx.: SimpleCursorAdapter with overrided
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
Cursor cursor = managedQuery(MobileTraderProvider.CONTENT_URI,
null, null, new String[] { constraint.toString() }, null);
return cursor;
}
then
customAdapter.getFilter().filter(filterText)
// it will call runQueryOnBackgroundThread
second use ContentProvider(it will manage curosors for you ... it will even requery if data changed)
EDIT:
first really use my advice
second before
for (int i = 0; i < count; ++i) {
c.moveToNext();
//...
add c.moveToFirst();
thrid: use
if(c.moveToNext())
{
int col = c.getColumnIndex("name");
//..... rest goes here
}
SECOND EDIT:
MyProvider.java
public class MyProvider extends ContentProvider {
static final String LTAG = "MyAppName";
public static final Uri CONTENT_URI = Uri.parse("content://my.app.Content");
static final int CARDS = 1;
static final int CARD = 2;
public static final String CARDS_MIME_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/Cards";
public static final String CARD_MIME_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/Cards";
static final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
static final HashMap<String, String> map = new HashMap<String, String>();
static {
//static "Constructor"
matcher.addURI(Constants.AUTHORITY, "Cards", LISTS);
matcher.addURI(Constants.AUTHORITY, "Cards/*", LIST);
map.put(BaseColumns._ID, "ROWID AS _id");
map.put(Tables.Cards.C_NAME, Tables.Cards.C_NAME);
map.put(Tables.Cards.C_NUMBER, Tables.Cards.C_NUMBER);
map.put(Tables.Cards.C_PATH, Tables.Cards.C_PATH);
}
private CardDatabaseOpenHelper mDB;
#Override
public boolean onCreate() {
try {
mDB = new CardDatabaseOpenHelper(getContext());
} catch (Exception e) {
Log.e(LTAG, e.getLocalizedMessage());
}
return true;
}
public int delete(Uri uri, String selection, String[] selectionArgs) {
String table = null;
switch (matcher.match(uri)) {
case CARD:
//overriding selection and selectionArgs
selection = "ROWID=?";
selectionArgs = new String[] { uri.getPathSegments().get(1) };
table = uri.getPathSegments().get(0);
break;
case CARDS:
//this version will delete all rows if you dont provide selection and selectionargs
table = uri.getPathSegments().get(0);
break;
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
int ret = mDB.getWritableDatabase().delete(table, selection, selectionArgs);
getContext().getContentResolver().notifyChange(Uri.withAppendedPath(CONTENT_URI, table), null);
return ret;
}
#Override
public String getType(Uri uri) {
switch (matcher.match(uri)) {
case CARDS:
return CARDS_MIME_TYPE;
case CARD:
return CARD_MIME_TYPE;
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
}
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
String table, rowid;
switch (matcher.match(uri)) {
case CARD:
//overriding selection and selectionArgs
selection = "ROWID=?";
selectionArgs = new String[] { uri.getPathSegments().get(1) };
table = uri.getPathSegments().get(0);
break;
case CARDS:
//this version will update all rows if you dont provide selection and selectionargs
table = uri.getPathSegments().get(0);
break;
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
int ret = mDB.getWritableDatabase().update(table, values, selection, selectionArgs);
getContext().getContentResolver().notifyChange(Uri.withAppendedPath(CONTENT_URI, table), null);
return ret;
}
public Uri insert(Uri uri, ContentValues values) {
String table = null;
switch (matcher.match(uri)) {
case CARDS:
table = uri.getPathSegments().get(0);
break;
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
mDB.getWritableDatabase().insert(table, null, values);
getContext().getContentResolver().notifyChange(Uri.withAppendedPath(CONTENT_URI, table), null);
return null;
}
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
switch (matcher.match(uri)) {
case CARDS:
builder.setTables(uri.getPathSegments().get(0));
break;
case CARD:
builder.setTables(uri.getPathSegments().get(0));
selection = "ROWID=?";
selectionArgs = new String[] { uri.getPathSegments().get(1) };
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
builder.setProjectionMap(map);
Cursor cursor = builder.query(mDB.getReadableDatabase(), projection, selection, selectionArgs, null, null, sortOrder);
if (cursor == null) {
return null;
}
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
}
CardCursorAdapter.java
class CardCursorAdapter extends SimpleCursorAdapter {
public MyCursorAdapter(Context context, int layout, Cursor c,
String[] from, int[] to) {
super(context, layout, c, from, to);
}
#Override
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
//search in cards.name
String selection = Tables.Cards.C_NAME + " LIKE ?";
String[] selectionArgs = new String[] {"%" + constraint.toString() + "%"};
Cursor cursor = managedQuery(Uri.withAppendedPath(MyProvider.CONTENT_URI, Tables.Cards.Name),
getCursor().getColumnNames(), selection, selectionArgs, null);
return cursor;
}
}
Tables.java
public static class Tables {
//table definition
public static interface Cards {
public static final String NAME = "cards";
public static final String C_NAME = "name";
public static final String C_NUMBER = "number";
public static final String C_PATH = "path";
}
//other tables go here
}
AndroidManifest.xml
</manifest>
</application>
<!-- ....... other stuff ....... -->
<provider android:name="MyProvider" android:authorities="my.app.Content" />
</application>
</manifest>
then in activity
onCreate(...){
listView.setAdapter(new CardCursorAdapter(this, R.layout.listrow,
managedQuery(Uri.withAppendedPath(MyProvider.CONTENT_URI, Tables.Cards.NAME),
new String[] { BaseColumns._ID, Tables.Cards.C_NAME, Tables.Cards.C_NUMBER, Tables.Cards.C_PATH },
null,null, number),
new String[] { Tables.Cards.C_NAME, Tables.Cards.C_NUMBER, Tables.Cards.C_PATH },
new int[] { R.id.tName, R.id.tNumber, R.id.tPath }));
}
OnClickListener searchListener = new OnClickListener() {
public void onClick(View v) {
DeckEditorScreen1.this.listView.getAdapter().getFilter().filter("text for search in name column of card table set me to empty for all rows");
}
}
Ok, I've done some testing and I think I know what was the problem. Java allows constructs like:
String[] whatever;
if (something) whatever = new String[avalue];
else whatever = new String[anothervalue];
The crash occurs if you don't assign a concrete value to each and every field whatever[i]. The rest of the code is now just fine, though I've added Selvin's correction
if (c.moveToNext) ...
c.moveToFirst() is not correctly used in my case, as the for iterates count times. If you perform a moveToFirst first, you're always missing the first element pointed by the cursor.