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
Related
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.
I am writing an application for managing students time table and I'm having problem with presentation. I want to have all items sorted by time they start and if there is next day there should be shown list separator with date.
#Override
public void bindView(View view, Context context, Cursor cursor) {
String day = cursor.getString(cursor.getColumnIndex("DAY"));
if(day.equals(lastDay)){
separator.setVisibility(View.GONE);
}else{
separator.setText(day + " " + dayOfWeek);
lastDay = day;
separator.setBackgroundColor(Color.parseColor("#0099CC"));
}
}
Unluckilly, I am facing 2 problems. 1st items are not sorted at all, maybe but 10 first, and 2nd it seems to me I have no control when each cell is drawn thats why algorithm with compering last day doesnt work at all and I have separator between every single entry, beside few first cells... Any ideas how can I solve this? (Btw. data is fetched from sqlite db)
Regards,
Robert
There is no sorting whatsoever in the code in your answer, so I cannot help you with the 1st issue.
BTW, if possible, you should sort your data on the SQLite level - it will be faster than any Java code. If it's not possible (for example your sorting criteria cannot be expressed as a sqlite query), you should sort the data before passing it to the adapter. In this case you should probably create your own BaseAdapter subclass.
The second issue is the expected behavior of CursorAdapter. The bindView are called on demand, so they will be called for the subsequent items when you're scrolling down, but when you start scrolling up, it will be called for the items appearing on the top of the screen. Read more about views recycling in Android adapters.
If you want to section your data I recommend creating an adapter with two view types - one for the section header, the other one for the actual item. Calculate the positions of added section items in swapCursor() call and call different code in newView/getView.
1st, you will need to sort your data by the day column. You need to do this when query the database and get the cursor. The sort string will look something like this:
"DAY ASC"
2nd, this is the really tricky thing. If you did this yourself you would have to remap the cursor positions and set different view types on getView like chalup said. I've made a library that does all of this for you though called SectionCursorAdapter. To implement your this for yourself extend SectionCursorAdapter and do the following.
#Override
protected Object getSectionFromCursor(Cursor cursor) {
int columnIndex = cursor.getColumnIndex("DAY");
String day = cursor.getString(columnIndex);
return day;
}
That will give you a new section for each day. Mattering on what you return you could make it by week or month as well.
Good day to all.
Is there some way to order the TableLayout content by a specific column?
I need to order the content according to user selection from a spinner.
Example if the user chooses to order the table by "name" the column name is arranged in ascending order.
The rows contain TextViews in which said content is saved, and the content is populated via a database.
Is there an easier way to order the content other than having to re-open a connection to the database?
Thanks.
The only way i can think of is to create a java object for your table row entries, putting all your objects into a list and comparing them using something like this:
Collections.sort(nodeList, new Comparator<DataNode>(){
public int compare(DataNode o1, DataNode o2){
if(o1.degree == o2.degree)
return 0;
return o1.degree < o2.degree ? -1 : 1;
}
});
It would be easier to re-open a connection to the database. You have to redraw your TableLayout in all cases.
I have implemented an autocomplete feature to allow the user to start typing a contact and for that contact to appear in a dropdown list using an autoCompleteTextView, like the same way it works when picking contacts for messages or e-mails.
As I don’t want a variable holding all of the contacts at once since this could be very big, I populated my ArrayList as the user enters letters into the contact field.
I am setting it up like this:-
peopleList = new ArrayList>();
adapter = new SimpleAdapter(this, peopleList, R.layout.customcontcell ,new String[] { "Name", "Phone" , "Type" }, new int[] { R.id.ccontName, R.id.ccontNo, R.id.ccontType });
txtPhoneNo.setAdapter(adapter);
Then when the user starts typing a name it grabs all the rows from the Contacts that match that, this is done in my function “QueryContacts” like so:-
selectionWhere = ContactsContract.Contacts.DISPLAY_NAME+" LIKE '" + name + "%'";
//Cursor to retrive contact details.
Cursor people = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, selectionWhere, null, null);
This cursor is then used to populate my peopleList which is being used by the Adapter.
In this state it works, however without some checks the above code ends up retrieving ALL of the contacts initially (as no name is entered so it filters nothing) and it runs every time a new letter is typed. This is jittery and slow.
So I wanted to put some simple checks to limited it to only retrieving the contacts when 2 letters have been entered, and not retrieving anymore unless it goes below 2 letters then back to 2 again.
So around my QueryContacts functions I add:-
if(name.length() < 2)
mGotContacts = false;
//If the length is two letters long and we haven't queried already, query for the name.
if(name.length() == 2 && mGotContacts == false)
{
// Cursor code
// Populate list with cursor data.
}
Problem is now the autocompleteTextView no longer drops down, I have checked and the variable peopleLists which populated my SimpleAdapter is being correctly updated.
So, am I doing this a daft way? Should I just grab all of the data in one go and let the AutoCompleteTextView filter it?
Is their a better way of doing this and why does it no longer work with those checks in my QueryContacts function?
Have a look at the threshold property of AutoCompleteTextView. The threshold defines the number of characters that must be entered before the autocomplete drop down is shown. I am not sure what effect this would have on performance, but since its native to android I imagine its as fast as reasonably possible.
setThreshold method documentation
I think it's kinda easy one but still I'm new to android programming so please have patience. I want to know how can I get the number of records (rows) in a specific table in my db. I need this so I can create a loop to go through every record and add each one of it to the specific Array and display it later on. This is the source:
db.openDataBase(); // open connection with db
Cursor c = db.getTitle(5); // loop here through db, right now I'm fetching only one record
startManagingCursor(c);
//adding areas to the list here
Area o1 = new Area();
o1.setOrderName(c.getString(1) + c.getString(2));
m_areas.add(o1);
db.close();
Does anyone can help me with this please? Thx in advance!
SELECT COUNT(*) FROM tablename
To get the number of rows in the cursor, use getCount.
To get the amount of total rows in a table, either use reinierposts solution, or do a select which select all rows in the table and get the count from the cursor. I'm guessing his solution is quicker though unless you actually need all the rows in the table.
Such a query would be:
SELECT * FROM footable;
You don't really need to get a count of how many first; instead, create a db.getTitles() function that returns all of the rows and returns a Cursor, then loop over the Cursor. Right now you probably have a query that looks something like SELECT ColumnA, ColumnB FROM Titles WHERE id = 5; just copy the function, remove the parameter and take off the WHERE clause so it looks like just SELECT ColumnA, ColumnB FROM Titles.
Then your code would look something like this:
db.openDataBase(); // open connection with db
Cursor c = db.getTitles();
startManagingCursor(c);
//adding areas to the list here
if (c != null && c.moveToFirst()) {
do {
Area o1 = new Area();
o1.setOrderName(c.getString(1) + c.getString(2));
m_areas.add(o1);
} while (c.next());
}
db.close();
We check if the function returned a cursor at all, then move to the beginning of the cursor and start looping, going to the next item each time through. For more information on the Cursor interface see the API here, or to learn more about database access and related design practices better in general I suggest going through the Notepad tutorial.