Determine Position in Android SQLite Database - android

I want to be able to find the position of a string in a column. I have an app where the user adds to a list which forms a card and when they swipe the card, it deletes. I'm new to SQLite and I'm having a bad time trying to delete the items I want.
Here's what I have so far:
c2.moveToFirst();
String contentLabel = c2.getString(c2.getColumnIndex("Content"));
db.delete("Lists", "Content = '" + contentLabel + "'", null);
Now the problem with this is that when I swipe the card away, say, on the third card, the first card gets removed and the card that was swiped away moves to the top of the list.

The most accurate way to delete the correct item from the Sqlite database is by specifying the unique ID of the item to be deleted.
Did you create your database with an _id column? If not you may be able to use Sqlite's default ROWID column instead - never tried it, but I believe that android automatically maps this to _id anyway.
You must add the ID number to your loader's projection, so that you have this value in your cursor when you fill your card views with data.
Assuming that your list items - or cards - are using a custom layout, you should have an implementation of CursorAdapter which fills the cards with data by either recycling an existing view, or creating a new view for each list item that is displayed.
When you populate each list item with data, in the cursor adapter, you should also call listItemView.setTag(cursor.getString(cursor.getColumnIndex('_id'))); on the card view. This will store the the associated Sqlite row id number in the card view. Which I believe is a Long.
In your item dismissed handler, you can then call listItemViewToDismiss.getTag(); to learn the ID number that you want to delete from your database. Note that we've stored this as a String, but View.getTag() will return an Object, so this will need to be cast back to string, like so:
String storedRowId = (String) listItemViewToDismiss.getTag();
Once you have the database row ID easily reachable, the rest is simple:
db.delete(URI, "_id=?", new String[] { (String) cardViewToDismiss.getTag() });
This will delete only the rows which have the unique id specified in the list item's tag - if you're using SQLite's AUTOINCREMENT on your _id column - which I hope you are - then this should only ever delete one row, or zero rows if it has already been deleted by another process.
If your content provider can handle URIs to individual items, then I think you can also insert the full URI of the current item (with an appended ID) into the view's tag and then simply call
db.delete( (String) viewToDismiss.getTag() );
... and let the content provider delete the correct item.
Some references
Cursor Adapter's getView method:
[http://developer.android.com/reference/android/widget/Adapter.html] (See getView() on that page)
Setting tags:
http://developer.android.com/reference/android/view/View.html#setTag(java.lang.Object)
disclaimer
It's been a while since I've done this, and I wrote that code from memory, so I hope someone will correct me if this is no longer the best practice for Android development, or if I've made a stupid error.

As your code is now, you always delete the first item. Move your cursor to the element you want to delete, with the method cursor.moveToPosition(int position).
Change your code to this:
// position = position of the "swiped card" (e.g. for the third card position is 3)
c2.moveToPosition(position - 1);
String contentLabel = c2.getString(c2.getColumnIndex("Content"));
db.delete("Lists", "Content = '" + contentLabel + "'", null);
moveToPosition(int position) returns false if it fails to move to the position (e.g. there is no such position), so you may want to add some code to check this:
if (!c2.moveToPosition(position - 1)) {
//failed to move!
}
String contentLabel = c2.getString(c2.getColumnIndex("Content"));
db.delete("Lists", "Content = '" + contentLabel + "'", null);

Maybe this might help?
String cardLabel;
card.setOnSwipeListener(new Card.OnSwipeListener() {
#Override
public void onSwipe(Card card) {
lable = "[code to get the swiped card's text]"
}
});
db.delete("Lists", "Content = '" + cardLabel + "'", null);
Basically just add some type of listener to get the text of the card as it is swiped and delete where the text equals that found by the listener. The issue with deleting by the text could be that the user might have two cards with the same text, maybe accidentally added it twice, but when they try to remove the duplicate, this would delete both.
Querying this way with the user defined text might also open you up to sql injection. I'm not sure how or if Android has any mechanisms to handle that, but it's worth thinking about. I agree with the others saying the proper way would be to search by ID. If you wanted to do an ID automatically, you could add something like this to the CREATE TABLE SQL statement in your DB helper.
Lists._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
Hope this was useful.

I thing you should use ormLite or greenDao and focus on your app and not fighting with sql.

Related

Android SQLite Update stops working after calling it two times

I have problems in updating rows in SQLite database in my Android application. It works successfully only, if I update it two times. But when I try to do it on the third time, it doesn't update the same row anymore.
LogCat doesn't show any exceptions. db.update() returns '1'.
I've searched similar issues on StackOverflow and the web. People advic]sed to remove db.close(); from database-helper, because I call it several times, or to use db.update method instead of db.rawQuery() or db.execSQL().
I also tested my query in SQLite client, and it works as it's supposed to.
Here is code of simple database-helper method:
public int updateEventDoneMark(Event event)
{
ContentValues args = new ContentValues();
args.put("completed", event.getCompleted());
return db.update("Event", args, "id" + "='" +event.getId() + "'", null);
}
Is there some SQLite-related issue I should know while I update one database entry several times in a row?
What does your content provider update and URI match look like?
Typical Content providers have a URI for each Table/View for a single row where _id is passed as a where_argument and a URI for multiple rows which uses where and where_arguments to select the rows to be updated.
Also it looks like you update by id. Android really want the id column named "_id", although I don't think is currently your issue, but it really depends on the URI it's using. Content Providers are usually coded with the _id and select by the column for a single row based on _id. That's why I want to see content provider. Your also selecting by the id yourself, this doesn't seem normal, although it could be accomplished, but not the norm. Typically the where part is something like 'colunm name = ?" and the next parameter where_arguments is a string array containing the value to replace the '?'.
Hope this helps.

SQLite Change Sort Order for items on database

So imagine this is a quick mockup of my database:
The items from the database are presented to the user per list, each list being displayed on a new fragment, which are displayed on a viewpager. So let's say in this hypotetical case, there would be two fragments on the viewpager, first fragment would display first_list and second fragment would display second_list. Here's the code for that query:
public static Cursor getListItems (final Context context, String listName) {
if (mDatabase == null || !mDatabase.isOpen())
open(context); //This gets the writable db.
String where = LIST_NAME + " = '" + listName + "'";
return mDatabase.query(TABLE_LIST_ITEMS, PROJECTION_LIST_ITEMS,
where, null, null, null, SORT_ORDER);
}
Where SORT_ORDER is order_in_list, this works well, to begin with.
Now, the listviews are re-arrangeable using a public library, which attempts to allow the user to control the order of the items in each list. Here's where I am having issues, there is no add(int index, Object object) for the cursor, or some other easy way to manage the sorting. I first thought I could simply call mDatabase.update() to change the value for order_in_list but that works, but the results are not as intended. For example, user drags item two to position zero, remeber: zero-index values, we would now have two items with order_in_list as zero. And although I can call mDatabase.update() on item one to update his position to one, imagine how much work that'd be to handle several items on a well-formed database.
Does anyone have any good suggestions on how I could work this out? I thought I had been clever by adding the extra col for sorting purposes :(
INB4:
Yes, I Know arrays handle this well. But the database doesn't only store 4 cols, it has many more fields. Populating arrays each time from the database would be a waste of time and effort. And I would, anyways, have to write back to the database when the app is closed.
EDIT So I changed the listview to only display one String of text, and further columns upon actual clicking on the item (and therefore displaying a new fragment with the specified list item data). This allowed me to simply keep an ArrayAdapter which easily handles the drag and drop. During onStop, I update the reference only if there was a change that required to be saved:
#Override
public void onStop() {
if (updateDbOnExit) {
//Update rows on database.
for (int i = 0; i < items.size(); i++) {
//Set the order in list be the actual order on the array.
Constants.LogMessage("Updating db content");
DbManager.moveListItemTo(getActivity(), items.get(i), i);
}
updateDbOnExit = false;
}
super.onStop();
}
Where MoveListItemTo updates the value for order_in_list:
public static void moveTaskItemTo (final Context context, String item, int to) {
if (mDatabase == null || !mDatabase.isOpen())
open(context);
String where = COL_CONTENT + " = '" + item+ "'";
ContentValues mContentValues = new ContentValues();
mContentValues.put(ORDER_IN_LIST, to);
int rows = mDatabase.update(TABLE_LIST_ITEMS, mContentValues, where, null);
Constants.LogMessage(rows + " row updated. Item moved to position: " + to);
close();
}
That will work for now. However, I am still interested on knowing if there is an alternate way, especially when for example, the adapter is using data from more than one column on the database, and is therefore required to use a CusorAdapter and not a regular ArrayAdapter, which in turn requires the Database itself to update upon each Drag and Drop to reflect the change on the UI via cursorAdapter.swapCursor(). As stated, updating ALL of the items on a database upon each drag (which realistically doesn´t happen that often btw), is expensive, updating only Two rows, would be a saner choice.
I just meant I wanted a more effective way to update the fields in the db, rather than manually updating each and every single row
Make the user-specified-order column a decimal, not an integer. Then you need to update only the moved row(s).
Allow negative numbers.
0.00 cat
1.00 aardvark
2.00 wolf
3.00 dog
If "dog" is dragged above "wolf" then "dog" becomes 1.50 and no need to change other rows. If "aardvark" is dragged above "cat" (special case -- prepending to list rather than inserting between rows) then subtract 1 from the topmost value, and "aardvark" becomes -1.00.
This will require you to know the values of the adjacent rows, but you won't have to update them. Only the moved row's value must change.
I would suggest that you have an additional column, user_specified_order which would represent the user's reordering of the rows in the UI via drag-drop.
You must update each row when its user_specified_order value is invalidated by the drag-drop repositioning. When to persist that value is up to you -- either at the "end" of the user's manipulations, however that be defined (e.g. click on Save button) or after each drag/drop if there is no clearcut UI indicator of "end of manipulation".
EDIT: ContenProvider in Android:
Android - Can you update a Cursor for SQLite results?
Android SQLite transactions:
Android Database Transaction

Android SQLite in app billing

I have a quiz app which populates a sqlite database with questions of around 20 different categories. I want to implement in app billing so that if someone purchases Category1 for example, then these questions are added to the database and no others. Some of my questions fall within two categories so let's say Category1 and Category2.
try {
for (int n = 1; n < sqlString.length; n++) {
db.execSQL("INSERT INTO " + DATABASE_TABLE + " VALUES ("
+ sqlString[n] + ");");
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
This is my current set up in the SQLite class onCreate method. sqlString is a string array containing all my 500 questions so far.
I'm going to store whether a category has been bought in another table (but I am open to other suggestions on how to do this). I plan on creating a class which reads this database setting up boolean values of true or false whether each category has been bought. So
boolean cat1 = CheckIfCategoryHasBeenBought(category1)
etc. Then if it has been bought I will implement a method such as
boolean[] catChecker = {cat1, cat2, cat3, etc....}
SQLite info = new SQLite(this);
info.open();
info.addQs(catChecker)
//this will pass the true and false boolean values for each method then
//based on that I choose to implement or not
info.close();
However I don't know if this is even a good way to do it. I'm not sure how to check if the value has already been added (as a result of it crossing over with another category that's been bought). I was thinking a cursor would be best to check if the value is already added however how do I get the cursor to search?
The ways I've thought this could be achieved is
1) I create a string array only with the strings associated with bought questions.
2) I create an if statement within the for loop above which checks whether the string is from a bought category
3) I give the value "null" to all strings that haven't been bought then add an if statement only executing the SQL if the sqlString[n] is not null.
Do you guys have any idea how it would be best to set this up?
Have you thought about starting with a full database - i.e. containing all questions that could be purchased by anyone - and then delete those that are no longer applicable?
There are lots of ways to do this, and I think there will be more than 1 good answer. If you have a server, you can do things like return a list of authorized databases/strings/etc. You can use that to reference different tables. You also can create a table or use sharedpreferences of which tables are downloaded. The list goes on.
The server option would add a layer of security. The stored table index would be easier to pirate, but also might be easier to implement and gives you the ability to use the categories offline.

ListView row id and position index confusion

I'm just starting to dive into some basic Android development and have been experimenting with a ListView and integrating it with a SimpleCursorAdapter. I look through a lot of online code samples, but I also have a book to use as a reference (Professional Android 2 Application Development).
In the book they work out an example To-Do list application that stores the list items in a SQLite database with an auto-incrementing, integer, primary key field.
A user can create new list items, but can also delete a selected item from the list. In the code, when the delete occurs, the primary key field is restricted (within the WHERE clause of the SQL statement) by the position attribute of the item as opposed to the item's rowid.
To me, this seems like an incorrect implementation. Looking at the SQLite documentation for AUTOINCREMENT, it says that this value will always increase and old values will never be re-used on the same table. So if you're deleting and adding things to the list, it would seem that the position and row id can get out of sync rather quickly.
Am I correct, then, to assume that the row id is the correct way to "index" into the database table and not the list position? I think the position would be safe to use if one is using the regular ListAdapter, but doesn't seem suitable when indexing into the database.
You can use the position to get a cursor to a particular list entry (and this cursor would be the 'row' in the 'table' corresponding to the row id):
Cursor cursor = (Cursor)parent.getItemAtPosition(pos);
int rowCol = c.getColumnIndex("_id");
Then you should see that cursor.getLong(rowCol) == id
That is definitely bad practice. I always use the row id to delete, and use the position id to retrieve the cursor's row id. I have the first edition of that book at home, I'm going to take a look at it myself later.

How to delete row from sqlite at listview Android

I have a listview and i am getting the data from sqlite database. My problem is to delete a row which user selected it from listview. I can delete all table by
dbConn.delete("Restobj", null,null);
But i cant delete a single row which is selected from listview.
Please Help
You essentially need to get the row id from the selected ListView item. Using the row id you can easily delete that row:
String where = "_id = " + _id;
db.delete(TABLE_NAME, where, null);
After deleting the row item make sure you get the ListView adapter cursor and do a requery. If you don't you will not see the updated table, hence updated ListView.
Make use of those other two parameters to the delete method. Take a look at the API documentation for more information.
http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html#delete%28java.lang.String,%20java.lang.String,%20java.lang.String[]%29
Pass in something other than null.
Also, try searching on stackoverflow and/or Google for this topic. The answers are plentiful.
You need to supply the appropriate values to the database object. I'm assuming that dbConn is an instance of a database object. If that is the case, you can pass in dbConn.delete() with 3 arguments. The first argument is the table name. The second is the whereClause. This is something similar to:
"id = " + objectDatabaseId
The final variable in this case you can leave blank. The end result is something like:
String whereClause = "id = " + objectDatabaseId;
dbConn.delete("Restobj", whereClause, null);
As a side note, it's better to use constants when referring to table names and table columns as apposed to "Restobj" you should have something like RestObject.TABLE_NAME where the constant is defined as a static final String inside of the RestObject.
-Randall

Categories

Resources