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.
Related
I have in my app a database with two tables : country and rights. Long story short, the db tells me whether a right (there is 10 rights in total) is legal or not in a specific country.
Now, I want the user to be able to search in my db by criterias. I have a layout with checkbox. If the user check a box, it mean he want to see every country in where the right is legal. For exemple, if he check the box "criteria1" and "criteria6", the user want the list of every country where criteria1 and criteria6 are legal, but we don't care wether the other rights are legal or not.
I asigned values to the checkboxs (1 if legal, 0 if illegal, just like in my db) and passes all of them to the activity who display the result of the search.
My problem is, I can't figure out how to search in my database. I need to only get the country where where the selected criters are equal to 1, but I don't know how to formulate my sql request (since I never know which criterias are going to be checked or not). My request need to only be about the criterias who has the value 1.
I had the idea of sending all my values to a function (witch returns a cursor) where I excecute a select statement if the value is equal to one, but I don't know how I could join all the result of my selects in a cursor. I also thought about using "CASE WHEN..." but it doesn't seem to work.
Does anyone have a clue on how I could deal with my search ?
If you need precisions on my problem, please ask.
This guy here (https://www.youtube.com/watch?v=NGRV2qY9ZiU&list=PL200JxfhYIgCrrpH4rCz-uNfBTb5sng1e) has the right idea.
The clip may be a bit slow but it does exactly what you want.
He creates a custom string based on if checkbox is checked and removes it from the string if unchecked.
To get what you want, you need to do a couple of things.
First, create a table with countries as rows, and rights as columns. Add 1 for right is present in country and 0 if not. Get this into an sqlite database (eg import via csv in DB browser for SQLite, free software; don't forget to create the android_metadata table in the sqlite database - search online for this). Import the database in the app (there is plenty of documentation for this online).
Second, change the text inputed in the if/else checkbox part of the script (he writes fruit names, you write for ex. "right1 = 1", or the exact query the checkbox should do on the column right1).
You also need to pay attention to the selection.add and selection.remove (know that selection is an array list which will store all your criteria for search by column).
Third, you need to change the content of his finalSelection (View view).
Delete all he has written and just create two strings:
String final1 = android.text.TextUtils.join(" or ", selection);
String final2 = "select country from table where " + final1;
The string final2 is your key for a cursor with a rawQuery. Just create a cursor in the database and pass the key to it. This can be done easily.
PS the method android.text.TextUtils.join() is amazing :)
You can place the operator of choice there.
If you need more than one operator (and, or etc), you can create different ArrayLists which you fill in the if/else checkbox is filled and join later in the finalSelection.
Oh, btw, if you have too many checkboxes, you will get a warning in the XML file (layout has more than 80 views is bad for performance).
In order to get around that, you need to get to know grid views a bit better. After reading a few tutorials on the basic use of GridViews, a good start for checkboxes inside them is here.
It may seem like a lot, but you need to learn to use holders to get information out of the getView of the modified BaseAdapter.
If you want to understand it better, follow the arrPath.
It is a String[] filled with all the paths of images found inside the cursor (string values from the dataColumnIndex, which contains paths of images).
Within the onClick() listener of the Button, from the arrPath he extracts only the rows of the cursor that were selected by checkbox click (thumbnailsselection[i] is a boolean - with a value TRUE/FALSE for each row in the cursor).
The selected paths are placed in the selectImages String, separated by OR.
The way my app is set up is that I have a ListView, maintained by a CursorLoader, of posts made by the users. Each post has user comments that are associated with it. Each listitem has a custom textview at the bottom where the comments can be scrolled through(with a right or left swipe). Within each of the custom textviews is a list of the comments associated with its particular post. Its kind of like the comments on the Google+ app except the items are scrollable. The comments are stored in a separate database table than the posts.
The issue I'm having is that each time BindView is called I'm querying the database and retrieving a cursor with the associated comments and adding it to the custom textview list. This seems really inefficient to query the comments database table for each item. So I'm wondering if there would be an ideal way to handle this. My code looks something like:
public void bindView(View view, Context context, Cursor cursor)
final String rowid = cursor.getString(cursor.getColumnIndexOrThrow(Database.ROWID));
Cursor commentcursor = c.getContentResolver().query(DatabaseProvider.CONTENT_URI_COMMENTTABLE, freeWriteCommentColumns, Database.PARENTPOSTROWID + " =?", new String[]{rowid}, null);
commentcursor.moveToFirst();
while (commentcursor.isAfterLast() == false){
//get comment
//add to comment text view list
}
I've looked into CursorJoiners but that doesn't seem to useful. I've played around with JOINS but that makes the number of rows way larger than it needs to be. I'm currently playing around with a holder for the comments table cursor that is created when the adapter is created and set as a global variable. This seems like a decent avenue because I don't have to requery each time. I'm just not sure how to handle this situation.
I know that u tried it but this is the way to go. I had almost the same problem, even a larger one becuase mine was with JOIN and then a MergeCursor. But lets get back to your issue: bindView() is called on the UI and you doing DB calls on the UI thread, this is bad practice and on top of that you are running all over cursor.
I would have JOIN the query for the post and concat the comments at the SQL level of the loading and then i would just use one cursor for the posts and comments and everything would be much much better.
How to concat the string at the SQL level? all the comments together? well this i will have look and edit my answer.
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
I am not sure it Android has this, or if there is a method to do so. I looked at the Doc, but haven't seen anything specifically for this use. Is there a preferred method that could be useful? Any help much appreciated.
I need it for an Agenda List with TextView Members. But when a user clicks on one, I need a way to access that agendas info, so like storing an ID from the database to reference that events info, but I need it hidden from the user.
Use setTag() and getTag(), you can pass it whatever type of Object you like.
Here's the reference.
Added from comment
I believe it is simplest to query the database a few times rather than have one Cursor holding everything.
For example, if we have a table with columns like: events(_id, name, startTime, endTime, people, etc). You could call SELECT * FROM events but that seems excessive if you only want the details on an event or two. It makes more sense to query SELECT _id, name FROM events and store this in a ListView. Then you can have:
listView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
detailCursor = database.rawQuery(`SELECT * FROM events WHERE _id = ?`, new String[] { id });
// Display this event's details somehow
}
}
A secondary cursor, like this, is simplest. You don't allocate memory for used event details and there isn't a significant performance loss from these repeated minor queries. In a case like this I wouldn't store anything in a View's tag, the general cursor (with every event's id and name) and the detail cursor should give you everything you need.
Hope that helps.
I was wondering if I have this situation:
In our SQLite databse, there are some tables (let's say four). each table consists of two columns: Title and Content.
We retrieved the titles from all the four tables, and display them in one ListView
The question is: How can we handle the onItemClickListener so that we can retrieve the Content of the selected item given that the items are from different tables?
I think I'll come across problem like that and I just want to know if it can be handled or not.
Thanks.
I'd say use an ArrayAdapter and wrap the data in a custom class that contains the table it came from (and ID etc if you need it), then you can get the ID and table by just knowing the index in the list.
The class could look something like this:
public static class ListItem {
public String title, table;
// Maybe include these as well?
public String content;
public int id;
#Override
public String toString() {
return title; // Or something else maybe?
}
}
Then simply build your data from the cursors into a ListItem[] and create the adapter like this:
new ArrayAdapter<ListItem>(data);
One problem with this solution is that you need to load everything in memory, if there is a lot of data you can create a custom Cursor which contains all four cursors and use a CursorAdapter instead.
The solution Nicklas A. proposed will do the trick. But if you have identical items why do you keep them in 4 different tables? If you items are identical it's betten to keep them in signle table and just to add some additional column to identify them if needed. If your items are not identical create 4 model classes for each of them and handle onItemClicked in each model.