Here's the ListAdapter that I am trying to bind my ListView with.
ListAdapter adapter = new SimpleCursorAdapter(this, R.layout.my_item,c,new String[] {"title","body"},
new int[] { R.id.TextView_Title,R.id.TextView_Body});
setListAdapter(adapter);
This works. But I am stuck at what I guess should be very simple. What I want to do is to display the title completely while I display a sub string of the body (say, first 10/15 chars). How do I do this in this case? Can I manipulate the cursor directly in a way so that it returns a substring at the first place or do I have to do it after the cursor has returned the values (in that case, how?). Thanks for the help.
EDIT: OK, sorry I thought you wanted to 'get' the substring of the body column but you want to 'set' it as a substring???
Implement SimpleCursorAdapter.ViewBinder on your activity and override setViewValue().
At some point in your code you'll need to use...
adapter.setViewBinder(this); // Put this on onCreate() perhaps
...and the code would look similar to this...
public class MyActivity extends Activity
implements SimpleCursorAdapter.ViewBinder {
#Override
public boolean setViewValue(View view, Cursor cursor, int column) {
int bodyColumn = cursor.getColumnIndex("body");
if (column == bodyColumn) {
String bodyString = cursor.getString(bodyColumn);
((TextView)view).setText(bodyString.subString(0, 10));
return true; // Return true to show you've handled this column
}
return false;
}
}
Related
I am reading data from an SQLite database using a cursor and using an adapter to display it in a listView. This works fine but I now want to reduce the amount of data that I show in the listView. At the moment it displays the following:
John Smith, 25, Moday, Consultation, Dr. Harley
Jane Doe, 41, Wednesday, Surgery, Dr. Pope
What I want it to display is:
John Smith, 25, Mo, Con, Harley
Jane Doe, 41, We, Sur, Pope
Basically I want to parse 3 of the strings. The problem is the cursor adapter takes the columns of the database as a string array in its constructor so I don't know where to perform the parsing operation on it. I've tried a number of different options and am getting unrecognised column id errors and other runtime errors. Can anyone point me in the right direction?
The method where the adapter is created:
private void fillList() {
Cursor c = db.getApts();
startManagingCursor(c);
String[] from = new String[] {ModuleDB.KEY_AptCode,
ModuleDB.KEY_AptName,
ModuleDB.KEY_AptAge,
ModuleDB.KEY_AptDay,
ModuleDB.KEY_AptType,
ModuleDB.KEY_AptDoc};
int[] to = new int[] {R.id.Aptcode_entry,
R.id.AptName_entry,
R.id.AptAge_entry,
R.id.Aptday_entry,
R.id.Apttype_entry,
R.id.Aptdoc_entry};
SimpleCursorAdapter aptAdapter =
new SimpleCursorAdapter(this, R.layout.apt_entry, c, from, to);
setListAdapter(aptAdapter);
}
1.) Let your activity implement - ViewBinder
2.) Match your column and use substring
public class YourActivity extends Activity
implements SimpleCursorAdapter.ViewBinder {
adapter.setViewBinder(this); //Put this line after your list creation and setlistAdapter
#Override
public boolean setViewValue(View view, Cursor cursor, int column) {
//Showing for day, similarly for others
int dayColumn = cursor.getColumnIndex(your day column name in quotes);
if (column == dayColumn ) {
String dayString = cursor.getString(dayColumn );
((TextView)view).setText(bodyString.subString(0, 3));
return true; // Return true to show you've handled this column
}
return false;
}
}
Also - #Simon is Right - Using a Custom Adapter that extends a Cursor Adapter is always better because you get a lot more freedom to modify it later if your requirements evolve further. Off the top of my head here is an example of how you can use custom adapter and build a nice list- http://www.androidhive.info/2012/02/android-custom-listview-with-image-and-text/
Not sure what adapter you use, but assuming all the data is shown as single row then I'd extend that one and override toString() method of it.
Don't think you can override toString() on a simple adapter but perhaps this will help?
SimpleCursorAdapter aptAdapter= new SimpleCursorAdapter(this, R.layout.apt_entry, c, from, to);
CursorToStringConverter stringConverter = new CursorToStringConverter() {
#Override
public CharSequence convertToString(Cursor cursor) {
return "Hello listview"; // whatever string you want to build using cursor.getString() etc
}
};
aptAdapter.setCursorToStringConverter(stringConverter);
[EDIT] Just checked the docs and SimpleCursorAdapter does not have a toString() method, nor do any of it's super classes.
I have a working implementation of a ContentProvider loading data via CursorLoader into a listview (with custom CursorAdapter). It's a list of events. Every item has a title, place, etc. but also a set of offers which should be displayed in a LinearLayout inside every list row.
The problem is that a Cursor row can only contain flat data, not a set of other items.
My only idea is to make a joined query on database like this:
SELECT * FROM events, offers WHERE events.id=offers.event_id;
But then I'll have as much rows as there are offers (and the list should display events, so it's not good) and the list would be overpopulated. Maybe there is a possibility to tell CursorAdapter to only populate list rows with unique events.id but somehow retrieve the offers data as well?
The best solution would be to put a Cursor or custom Object containing offers inside the events Cursor. But afaik it's not possible.
I was facing the same problem. In fact, I think a lot of people are.
The whole mechanism of URI - to Relational DB through contentprovider, and everything that was built around it (like the various change listeners, file and stream handling) - this is all very impressive and useful, but for very simple data models.
Once your application needs a more elaborate data model, like - a hierarchy of tables, object relational semantics - this model breaks.
I've found a bunch of ORM tools for Android, but they seem too 'bleeding edge' to me (plus, for the life of me, I couldn't figure out if they have data change notification support).
ORM is very common today, I really hope the Android folks agree and add ORM capabilities to the platform.
This is what I ended up doing:
A cursor of cursors, with a a leading index cursor that helps choose the correct internal curosr.
It's kind of a temp solution, I just needed to move on with my code and get back to this later. Hope this helps.
Of course if you use a listview, you probably need to also create a custom adapter to inflate the correct views, and do the binding.
public class MultiCursor implements Cursor {
private final String TAG = this.getClass().getSimpleName();
ArrayList<Cursor> m_cursors = new ArrayList<Cursor>();
Map<Long, CursorRowPair> m_idToCursorRow = Collections.synchronizedMap(new HashMap<Long, CursorRowPair>());
Set<Long> m_idSet = new HashSet<Long>();
Cursor m_idCursor;
/**
* #precondition: _id column must exist on every type of cursor, and has to have index of 0 (be the first)
* #param idCursor
*/
public MultiCursor(Cursor idCursor) {
m_idCursor = idCursor;// this cursor binds the order (1,2,3) to ids
// go over all the ids in id cursor and add to m_idSet
initIdSet();
// m_cursors.add(idCursor);
// m_position = -1;
}
private void initIdSet() {
m_idSet.clear();
long id;
m_idCursor.moveToPosition(-1);
while (m_idCursor.moveToNext()) {
id = m_idCursor.getLong(m_idCursor.getColumnIndex(ContentDescriptor.ShowViewItem.Cols.ID));
m_idSet.add(id);
}
m_idCursor.moveToFirst();
}
public void addCursor(Cursor cursor) {
// when something changes in the child cursor, notify parent on change, to notify subscribers
// cursor.registerContentObserver(new SelfContentObserver(this)); // calls my onchange, which calls the ui
m_cursors.add(cursor);
updateIdToCursorMap(cursor);
}
private class CursorRowPair {
public final Cursor cursor;
public final int row;
public CursorRowPair(Cursor cursor, int row) {
this.cursor = cursor;
this.row = row;
}
}
private void updateIdToCursorMap(Cursor cursor) {
// get object_type
// for each row in cursor, take id, row number
// add id, <cursor,rowNum> to map
long id;
int row = 0;
cursor.moveToPosition(-1);
while (cursor.moveToNext()) {
id = cursor.getLong(cursor.getColumnIndex(ContentDescriptor.ShowViewItem.Cols.ID));
if (m_idSet.contains(id)) m_idToCursorRow.put(id, new CursorRowPair(cursor, row));
row++;
}
cursor.moveToFirst();
}
private Cursor getInternalCursor() {
if (getPosition() < 0 || getCount()==0) return m_idCursor; // todo throw a proper exception
// get the id of the current row
long id = m_idCursor.getLong(m_idCursor.getColumnIndex(ContentDescriptor.BaseCols.ID));
CursorRowPair cursorRowPair = m_idToCursorRow.get(id);
if (null == cursorRowPair) return null;
Cursor cursor = cursorRowPair.cursor;
int row = cursorRowPair.row;
cursor.moveToPosition(row);
return cursor;
}
// //////////////////////////////////////////////
#Override
public void close() {
Log.d(TAG, "close");
for (Cursor cursor : m_cursors) {
cursor.close();
}
m_idCursor.close();
}
#Override
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
Log.d(TAG, "copyStringToBuffer");
getInternalCursor().copyStringToBuffer(columnIndex, buffer);
}
etc etc etc.
In you're adapter query the offers cursor for all records and make it a class variable. Then in your getView use the event id to iterate through the offer cursor and add the necessary textviews to your row layout when it find an appropriate match. It's not elegant, but it should work.
Unfortunately one CursorLoader can only load one Cursor. So the solution was to write a custom AsyncTaskLoader which returned two Cursors.
I am using an AutoCompleteTextView to show a list of items the user can select. When the users selects an item, this selected item fills a ListView just below the AutoCompleteTextView. So far so good.
The issue: after the selection of the item out of the AutoCompleteTextView, the AutoCompleteTextView body itself (this "text-box") gets filled up with some text, which is the SimpleCursorAdapter resource (the actual text showing up is: android.widget.SimpleCursorAdapter#4107d010).
What I wish to have: I want the AutoCompleteTextView to refresh and show no text in its own body so the user can immediately type in more text and select further items out of the drop-down list.
Could you please give me a hint how I could achieve that?
Added information:
Thank you Kyle. What I did was to extend SimpleCursorAdapter to SimpleCursorAdapterNoText. I then overridden convertToString() just like you said. I didn't change BindView because I read the documentation twice but I still don't understand what I should change in BindView. Any way - This didn't save the problem - I still get the same string in the AutoComplete. Here is my code:
#SuppressWarnings("deprecation")
private void populateListView()
{
// Get all of the notes from the database and create the item list
Cursor tournamentXCursor = mDbHelper.retrieveTrounamentX(mRowId);
startManagingCursor(tournamentXCursor);
// Create an array to specify the fields we want to display in the list (only name)
String[] from = new String[] {StournamentConstants.TblX.TBL_COLUMN_X_NAME};
// and an array of the fields we want to bind those fields to (in this case just name)
int[] to = new int[]{R.id.competitor_row};
// Now create an array adapter and set it to display using our row
SimpleCursorAdapterNoText tournamentX = new SimpleCursorAdapterNoText(this, R.layout.competitor_row, tournamentXCursor, from, to);
tournamentX.convertToString(tournamentXCursor);
setListAdapter(tournamentX);
}
Anyone has a clue what I am doing wrong?
EDITED:
This is my inherited SimpleCursorAdapter class
public class SimpleCursorAdapterNoText extends SimpleCursorAdapter
{
public SimpleCursorAdapterNoText(Context context, int layout, Cursor c,
String[] from, int[] to)
{
super(context, layout, c, from, to);
// TODO Auto-generated constructor stub
}
#Override
public CharSequence convertToString(Cursor cursor)
{
//Empty string so AutoComplete shows no text
return "";
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
// TODO Auto-generated method stub
super.bindView(view, context, cursor);
}
}
I changed my calling code and eliminated
tournamentX.convertToString(tournamentXCursor);
I was convinced it is essential that I not only override it in my subclass but that I also use it in my calling code so the text inside the AutoComplete will be eliminated.
I am sag to say the this still didn't help - I keep on getting android.database.sqlite.SQLiteCursor#41377578 in the AutoCompleteBox just after I select one item off the AutoComplete selection list.
Thanks D.
If you simply want to clear the text when a user clicks on an item in the dropdown list, define the AutoComplete as a member variable and override it's setOnItemClickListener. Like this:
mAutoComplete.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mAutoComplete.setText("");
}
});
if you are using a simplecursoradapter to fill the textView, you will need to subclass it and override the following method. You will probably also have to override bindview.
#Override
public CharSequence convertToString (Cursor cursor){
return "";
}
You need to put the above inside your SimpleCursorAdapterNoText class, not call it from the top level code.
class extends SimpeCursorAdpaterNotText{
// ... whatever other code you have here
#Override
public CharSequence convertToString (Cursor cursor){
return "";
}
}
i'm making select statement and it works fine
the problem is selecting more than thousands of records ate one and this cause the program too slow.
is there a possibility to select fifty by fifty and when select the first fifty record show them then add the next fifty record to them.
how can i do that .
thanks in advance ...
Use LIMIT/OFFSET Clauses is selection statement
I haven't worked on that but can give some idea reagarding that. You can use AsynTask here. In the doingInbackground() you can get the records and then you can call publishProgress() when 50 records are fetched and update the UI.
UPDATE:
You can use the LIMIT/OFFSET clause that Kiran said to get the limit of the record fetched and can update the UI using AsyncTask.
Here is the code you need to back your AutoCompleteTextView with a cursor adapter.
#Override
protected void onResume() {
super.onResume();
text = (AutoCompleteTextView) findViewById(R.id.autoCompleteTextView1);
final AdapterHelper h = new AdapterHelper(this);
Cursor c = h.getAllResults();
startManagingCursor(c);
String[] from = new String[] { "val" };
int[] to = new int[] { android.R.id.text1 };
CursorAdapter adapter = new MyCursorAdapter(this,
android.R.layout.simple_dropdown_item_1line, c,
from, to);
adapter.setFilterQueryProvider(new FilterQueryProvider() {
public Cursor runQuery(CharSequence constraint) {
if (constraint == null) {
return h.getAllResults();
}
String s = '%' + constraint.toString() + '%';
return h.getAllResults(s);
}
});
text.setAdapter(adapter);
}
class MyCursorAdapter extends SimpleCursorAdapter {
public MyCursorAdapter(Context context, int layout, Cursor c,
String[] from, int[] to) {
super(context, layout, c, from, to);
}
public CharSequence convertToString(Cursor cursor) {
return cursor.getString(cursor.getColumnIndex("val"));
}
}
The database that I am using has 3k rows of data in it and the Autocomplete works fine.
The things to note are that you need make sure that the your adapter puts the correct value in the text box once a user selects it. Do this with the convertToString method (at the end of the snippet above). You get to this method by extending SimpleCursorAdapter and overriding the method as shown.
Then you need to provide a FilterQueryProvider to your adapter. This allows your query to be run with the where clause of your typed text. If you have a huge dataset, then setting the threshold large enough (either programatically, or in xml) will prevent the filter query running until it will return a suitably sized resultset.
Hope this is useful.
Anthony Nolan
I am using the following code to set the adapter (SimpleCursorAdapter) for an AutoCompleteTextView
mComment = (AutoCompleteTextView) findViewById(R.id.comment);
Cursor cComments = myAdapter.getDistinctComments();
scaComments = new SimpleCursorAdapter(this,R.layout.auto_complete_item,cComments,new String[] {DBAdapter.KEY_LOG_COMMENT},new int[]{R.id.text1});
mComment.setAdapter(scaComments);
auto_complete_item.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
and thi is the xml for the actual control
<AutoCompleteTextView
android:id="#+id/comment"
android:hint="#string/COMMENT"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="18dp"/>
The dropdown appears to work correctly, and shows a list of items. When I make a selection from the list I get a sqlite object ('android.database.sqlite.SQLiteCursor#'... ) in the textview.
Anyone know what would cause this, or how to resolve this?
thanks
Ok I am able to hook into the OnItemClick event, but the TextView.setText() portion of the AutoCompleteTextView widget is updated after this point. The OnItemSelected() event never gets fired, and the onNothingSelected() event gets fired when the dropdown items are first displayed.
mComment.setOnItemClickListener( new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
// TODO Auto-generated method stub
SimpleCursorAdapter sca = (SimpleCursorAdapter) arg0.getAdapter();
String str = getSpinnerSelectedValue(sca,arg2,"comment");
TextView txt = (TextView) arg1;
txt.setText(str);
Toast.makeText(ctx, "onItemClick", Toast.LENGTH_SHORT).show();
}
});
mComment.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
Toast.makeText(ctx, "onItemSelected", Toast.LENGTH_SHORT).show();
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
// TODO Auto-generated method stub
Toast.makeText(ctx, "onNothingSelected", Toast.LENGTH_SHORT).show();
}
});
Anyone alse have any ideas on how to override the updating of the TextView?
thanks
patrick
I don't think you should have to update the text for the AutoCompleteTextView. It should do it automatically. It does this by calling the [CursorAdapter.convertToString(...)][1] method. if you read the description of the method it points this out. So if you were writing your own CursorAdapter you would override that method to return whatever text you would want to show up in the list of suggestions. This guy does a good job of explaining how to do it:
Line 86 - http://thinkandroid.wordpress.com/2010/02/08/writing-your-own-autocompletetextview/
However, since you are using a SimpleCursorAdapter, you can't override this method. Instead you need implement/create a [SimpleCursorAdapter.CursorToStringConverter][2] and pass it into [SimpleCursorAdapter.setCursorToStringConverter(...)][3]:
SimpleCursorAdapter adapter = new SimpleCursorAdapter(context, layout, cursor, from, to);
CursorToStringConverter converter = new CursorToStringConverter() {
#Override
public CharSequence convertToString(Cursor cursor) {
int desiredColumn = 1;
return cursor.getString(desiredColumn);
}
};
adapter.setCursorToStringConverter(converter);
Or if you don't want to create a CursorToStringConverter then use the [SimpleCursorAdapter. setStringConversionColumn(...)][4] method. But I think you still have to explicitly set the CursorToStringConverter to null:
int desiredColumn = 1;
adapter.setCursorToStringConverter(null);
adapter.setStringConversionColumn(desiredColumn);
Sorry, but the spam blocker won't let me post the links to the Android Documentation that describes the links I posted above. But a quick google search will point you to the correct doc pages.
[Late answer, just for the record. EDITed to remove my suggestion that subclassing is necessary.]
To use SimpleCursorAdapter with an AutoCompleteTextView, you need to set two handlers on the adapter: The CursorToStringConverter, and the FilterQueryProvider. Pseudocode follows:
adapter.setCursorToStringConverter(new CursorToStringConverter() {
public String convertToString(android.database.Cursor cursor) {
// Assume that "someColumn" contains the strings that we want to
// use to identify rows in the result set.
final int columnIndex = cursor.getColumnIndexOrThrow("someColumn");
final String str = cursor.getString(columnIndex);
return str;
}
});
adapter.setFilterQueryProvider(new FilterQueryProvider() {
public Cursor runQuery(CharSequence constraint) {
// runSomeQuery will look for all rows in the database
// that match the given constraint.
Cursor cursor = runSomeQuery(constraint);
return cursor;
}
});
When I make a selection from the list
I get a sqlite object
('android.database.sqlite.SQLiteCursor#'...
) in the textview.
You do not say what this "textview" is or how it relates to the Spinner.
I am going to take an educated guess and assume that you are simply assigning the selected item out of the Spinner into the TextView.
The selected item from a Spinner using a SimpleCursorAdapter is a Cursor, pointing at the row the user selected. The toString() implementation of Cursor will give you something akin to android.database.sqlite.SQLiteCursor# depending on where the Cursor came from.
More likely, you are going to want to call getString() on that Cursor, to retrieve some column value, and assign it to the TextView in question.
To solve the problem I just extended SimpleCursorAdapter and implemented the method convertToString(). Then I created an instance and set it as the adapter.
In order to allow filtering in AutoCompleteTextView when using CursorAdapters I also used setFilterQueryProvider(). See this question.
My extended class inside the Activity looks as:
private static class AutoCompleteCursorAdapter extends SimpleCursorAdapter {
public AutoCompleteCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
super(context, layout, c, from, to);
}
#Override
public CharSequence convertToString(Cursor cursor) {
// This is the method that does the trick (return the String you need)
return cursor.getString(cursor.getColumnIndex("name"));
}
}