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'm trying to get my contacts on a list view. Now I know that using simple_list_item_multiple_choice enables me to select multiple contacts, but it views names only without numbers.
On the other side, simple_list_item_2 can be used to show both name and number, but supports selection of only one contact.
Is there any template that combine them both? If not, how could I build my customized list with both features?
EDIT: This is the code I'm using
CursorLoader cl = new CursorLoader(this,ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PROJECTION, null, null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME+" ASC");
Cursor c = cl.loadInBackground();
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_multiple_choice, // Use a template
// that displays a
// text view
c, // Give the cursor to the list adapter
new String[] { ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME},
new int[] { android.R.id.text1},0);
setListAdapter(adapter);
Here, the second parameter of SimpleCursorAdapter is simple_list_item_multiple_choice but it supports only dealing with android.R.id.text1. So I can use items only, not subitems.
But in the following code
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_expandable_list_item_2, // Use a template
// that displays a
// text view
c, // Give the cursor to the list adapter
new String[] { ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ,ContactsContract.CommonDataKinds.Phone.NUMBER},
new int[] { android.R.id.text1,android.R.id.text2},0);
I can give it both ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME and NUMBER to be written in android.R.id.text1 and android.R.id.text2, but can't use multiple choice feature.
As Dipu said, you should make your own customized layout.
To show name and contact, you need two text views, and one check box for checking.
You can start coding from this tutorial:
http://www.mysamplecode.com/2012/07/android-listview-checkbox-example.html
Add one more text view to country_info.xml will solve your problem.
ADDED
To use a custom list view layout, you have to implement your own adapter.
This tutorial (2. Custom ArrayAdapter example) will help you figure out how to do that.
http://www.mkyong.com/android/android-listview-example/
The answer provided by Heejin is excellent, but it's not important to implement a custom ArrayAdaptor. What only I needed to do is to write a custom layout and send it to the SimpleCursorAdaptor constructor.
The custom layout represent the layout of each item in the list view. I need each row to contain a CheckedTextView and another small TextView as a subitem. So I've made a layout called row_view.xml with the following content
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<CheckedTextView
android:id="#+id/checkedTextView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:text="CheckedTextView" />
<TextView
android:id="#+id/textView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Small Text"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
Then I've just used it in the constructor
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.row_view, // Use a template
// that displays a
// text view
c, // Give the cursor to the list adapter
new String[] { ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER},
new int[] { R.id.checkedTextView, R.id.textView},0);
setListAdapter(adapter);
This is the full code
public class MultipleContacts extends ListActivity implements OnItemClickListener {
private static final String[] PROJECTION = new String[] {
ContactsContract.CommonDataKinds.Phone._ID
,ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
,ContactsContract.CommonDataKinds.Phone.NUMBER
};
SimpleCursorAdapter adapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.multiple_contacts);
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
getListView().setOnItemClickListener(this);
// Get a cursor with all people
CursorLoader cl = new CursorLoader(this,ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
PROJECTION, null, null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME+" ASC");
Cursor c = cl.loadInBackground();
adapter = new SimpleCursorAdapter(this,
R.layout.row_view, // Use a template
// that displays a
// text view
c, // Give the cursor to the list adapter
new String[] { ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER},
new int[] { R.id.checkedTextView, R.id.textView},0);
setListAdapter(adapter);
}
#Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
//what to do when an item is clicked
CheckedTextView checkedTextView = (CheckedTextView) v.findViewById(R.id.checkedTextView);
Toast.makeText(this, checkedTextView.getText(), Toast.LENGTH_SHORT).show();
}
}
Please note that I've two layouts, one for the list view itself(called multiple_contacts) and the provided layout here (called row_view) is the layout for each item in the list view. All what I need from multiple_contacts is to write setContentView(R.layout.multiple_contacts);
I was looking for an alternative to a spinner, since the first item is always selected (which causes me issues), and I found some examples for using an AlertDialog with a list instead.
I am having two problems:
The list is displaying and is formatted ok, but there are no values in it. I know the query is returning, and the cursor/adapter has the data in it.
This may be a symptom of #1 - but when I select a blank row, the Cursor cursor2 = (Cursor) ((AdapterView) dialog).getItemAtPosition(which); statement causes a crash (it's a ClassCastException).
I had similar code previously which set the adapter to a spinner object, and the data was displaying fine.
I don't think the adapter is getting set correctly, and I have been unable to come up with a solution thus far.
Any thoughts?
Thanks!
btnDenomination.setOnClickListener(new View.OnClickListener()
{
public void onClick(View w)
{
Cursor cursor = coinDB.myDataBase.rawQuery("select _id, denomination_desc from denomination", null); // must select the _id field, but no need to use it
startManagingCursor(cursor); // required in order to use the cursor in
String[] from = new String[] {"denomination_desc" }; // This is the database column name I want to display in the spinner
int[] to = new int[] { R.id.tvDBViewRow }; // This is the TextView object in the spinner
cursor.moveToFirst();
SimpleCursorAdapter adapterDenomination = new SimpleCursorAdapter(CoinsScreen.this,
android.R.layout.simple_spinner_item, cursor, from, to );
adapterDenomination.setDropDownViewResource(R.layout.db_view_row);
new AlertDialog.Builder(CoinsScreen.this)
.setTitle("Select Denomination")
.setAdapter(adapterDenomination, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
Cursor cursor2 = (Cursor) ((AdapterView<?>) dialog).getItemAtPosition(which);
strDenomination_id = cursor2.getString(0); // Gets column 1 in a zero based index, the first column is the PKID. this could
// be avoided by using a select AS statement.
Log.d("Item Selected", strDenomination_id );
TextView txtDenomination = (TextView) findViewById(R.id.textDenomination);
txtDenomination.setText(cursor2.getString(1));
dialog.dismiss();
}
}).create().show();
}
});
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:id="#+id/LinearLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView android:text=""
android:id="#+id/tvDBViewRow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF0000" />
</LinearLayout>
Are you sure R.id.tvDBViewRow is the id of the TextView in the layout android.R.layout.simple_spinner_item ? From this, the TextView's id should be android.R.id.text1.
So new answer for the second issue :)
I think you should reuse the initial cursor instead of trying to get a new one... Can you try to do :
adapterDenomination.moveToPosition(which);
strDenomination_id = adapterDenomination.getString(0);
in the onClick() ?
Basically I'm trying to make a contact list like the one provided by Android. When populating the listview with items, using a SimpleCursorAdapter you can easily get all the names to appear in the R.id.textview of each item:
private void fillData() {
mCursor = mDbAdapter.fetchAllContacts();
startManagingCursor(mCursor);
String[] from = new String[] {DBAdapter.KEY_NAME};
int[] to = new int[] {R.id.contact_name};
SimpleCursorAdapter contacts = new SimpleCursorAdapter(this, R.layout.contact_view, mCursor, from, to);
this.setListAdapter(contacts);
}
Something like that. I've searched and found sample code for both getting images from online, or displaying a set number of images in the items (for instance you know you have 5 items so you get the 5 matching images). But I really don't know where I'd begin on getting images from my SD card, and displaying them in the proper item. The images are named according to the id of the contact, so I do have the means to call the proper image.
A push in the right direction would be much appreciated, thank you!
EDIT: #Jeff Gilfelt gave a great answer, but I went ahead and spoke too soon when saying I could figure out the rest on my own... haha. I have a default image declared in the xml for the contacts like Android does. When I implement the new Adapter, it like compresses the items into nothing, I figure because it finds an empty bitmap # that location. So I did the following:
#Override
public void setViewImage(ImageView v, String id) {
File root = Environment.getExternalStorageDirectory();
File path = new File(root, "path/images/thumbs/"+id+".jpg");
if(path.exists()) {
Bitmap bitmap = BitmapStatic.getThumb(id);
v.setImageBitmap(bitmap);
}
else {
super.setViewImage(v, id);
}
}
But this doesn't help either. Any ideas?
EDIT2: Figured out the above problem. Simply go like this:
else {
Resources res = mContext.getResources();
Drawable drawable = res.getDrawable(R.drawable.default);
v.setImageDrawable(drawable);
}
Hope this helps others! Remember for this solution you will have to add a Context member var, and the line mContext = context in the constructor.
Create a subclass of SimpleCursorAdaptor and override the setViewImage method such that it constructs the path to the appropriate file on the SD card from the id you feed it, then use BitmapFactory.decodeFile to create a Bitmap you use for the image view you are binding to:
public class MySimpleCursorAdapter extends SimpleCursorAdapter {
public MySimpleCursorAdapter(Context context, int layout, Cursor c,
String[] from, int[] to) {
super(context, layout, c, from, to);
}
#Override
public void setViewImage(ImageView v, String id) {
String path = "/path/to/sd/card/" + id + ".png";
Bitmap b = BitmapFactory.decodeFile(path);
v.setImageBitmap(b);
}
}
Then add your contact id from the cursor and the view id of your ImageView to the to/from arrays that you pass to the adapter. Example:
private void fillData() {
mCursor = mDbAdapter.fetchAllContacts();
startManagingCursor(mCursor);
String[] from = new String[] {DBAdapter.KEY_NAME, DBAdapter.ID};
int[] to = new int[] {R.id.contact_name, R.id.contact_image};
MySimpleCursorAdapter contacts =
new MySimpleCursorAdapter(this, R.layout.contact_view, mCursor, from, to);
this.setListAdapter(contacts);
}
Read this tutorial http://developer.android.com/guide/topics/data/data-storage.html
and this post http://www.brighthub.com/mobile/google-android/articles/64048.aspx
Have a two dimensional arraylist, the first element containing the contact name and the second containing the image.After you read the images into a bitmap, save the bitmap in your contacts arraylist. Have a custom arrayAdapter which contains a textView and a imageView as its row elements, set the contact name and image in the getView() of the array adapter. Refer this post for custom listViews http://www.androidpeople.com/android-custom-listview-tutorial-example/
This should help you get going.
You should read up on the external storage chapter on the Android development guide. By calling getExternalFilesDir() you will get a filepath to your SD card.
As a strategy, if you navigate to the folder containing the images and just pass in the ID of your contact to locate the file, you can load this into a data structure.
You can then create a custom adapter passing in the data structure containing your images. This will result in your ListView to contain only the images of your contacts.
This is just to give you a head start!
You don't need to create a subclass of SimpleCursorAdapter, just create a ViewBinder which handles the set up of the your image for each row.
ViewBinder.setViewValue will be called for all Views in your row (as specified in your layout xml) so make sure you return false for any view which is not an instance of ImageView.
ViewBinder viewBinder = new ViewBinder(){
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
if (view instanceof ImageView){
ImageView imageView = (ImageView) view;
String imageFilename = cursor.getString(columnIndex);
imageView.setImageURI(imageFilename);
return true;
}
return false;
}
};
cursorAdapter.setViewBinder(viewBinder);
I'm working on my first Android app and can't figure out how to get my SimpleCursorAdpater to populate the view. The cursor that I'm passing in has results in it, so the problem must be somewhere in instantiating the adapter or in binding it to the view. I'm sort of at my wits end since no exceptions are thrown and I can't really step
into setListAdapter.
Here is how i get my cursor in the first place:
Searches searches = new Searches(this);
SQLiteDatabase db = searches.getReadableDatabase();
//select _id, Name, Search FROM Searches;
Cursor c = db.query(
SearchConstants.TABLE_NAME,
FROM, null, null, null,
null, null);
startManagingCursor(c);
And this is the schema do my db:
CREATE TABLE Searches (_id INTEGER PRIMARY KEY, Name Text, Search TEXT)
Here are the two lines where things start to fall apart:
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.search, cursor, FROM, TO);
setListAdapter(adapter);
My main layout looks like this:
<ListView
android:id="#android:id/android:list"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="#android:id/android:empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/empty" />
Here is the view to fill with each result:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10sp">
<TextView
android:id="#+id/_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="#+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="#+id/colon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=": "
android:layout_toRightOf="#id/name" />
<TextView
android:id="#+id/search"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:textStyle="italic"
android:layout_toRightOf="#id/colon" />
</RelativeLayout>
Finally here are the static variables I used:
//search query stuff
private static String[] FROM = {SearchConstants._ID, SearchConstants.NAME_COLUMN, SearchConstants.SEARCH_COLUMN};
//where to paste search results
private static int[] TO = {R.id._id, R.id.name, R.id.search};
/**
* Table name
*/
public static final String TABLE_NAME = "Searches";
/**
* Name Column
*/
public static final String NAME_COLUMN = "Name";
/**
* Search Column
*/
public static final String SEARCH_COLUMN = "Search";
I think this is all of the relevant code. I have no idea how to proceed at this point, so any suggestions at all would be helpful.
Thanks,
brian
PS: Looks like theres a lot of great suggestions here - i'm not ignoring them i just havent had the chance yet. Thanks for the advice! At some point i'll go thru them all and try to give some feedback as to which things worked well for me.
You can step into the code if you have the source code. Luckily, Android is open source. To easily attach source code in Eclipse, see:
http://android.opensourceror.org/2010/01/18/android-source/
As for the problem itself, you said in a comment above that you iterate all of the items before creating the adapter. If you are not creating a new cursor after iteration, you probably need to rewind it or the adapter might think it's empty.
cursor.moveToFirst()
Please don't worry about any internal binding aspects. I'm sure there is an easy way out. Try the following:
First, just to ensure your cursor really has got data where it's needed, put the line
System.out.println("cursor.getCount()="+cursor.getCount());
right before the call of setAdapter(). But surely, you already tested to get a row count ;-) So the following might be more interesting.
To check if your binding fails, please test with:
android:id="#+id/android:list"
instead of :
android:id="#android:id/android:list"
in your main.xml. Same thing with: android:id="#+id/android:empty".
And if you still don't get results, you can also try using a list default xml-layout (like simple_list_item_1) for displaying, which would look like this:
ListAdapter adapter = new SimpleCursorAdapter(this,
// Use a template that displays a text view
android.R.layout.simple_list_item_1,
// Give the cursor to the list adapter
cursor,
// Map the NAME column in your database to...
new String[] {SearchConstants.NAME_COLUMN} ,
// ...the "text1" view defined in the R.layout.simple_list_item_1
new int[] {android.R.id.text1}
);
Just copy paste it into your activity and see what happens.
Hope you're done with that!
Just got the same problem and found how to allow the Simplecursoradapter creation to not fail.
In your cursor, the query of the database MUST contain the table primary key even if you don't need it ! If not it will fail and crash...
Hope it will help others with the same problem !
Alright, I noticed you used a column name with a capital letter. Make sure you use the exact identifier in the DB scheme (the sqlite column names are case sensitive). But in the code you provided the column identifiers match.
So, if the cursor you use really has got the data, try out the above code at first (instead of some custom layout) for creating a SimpleCursorAdapter and it should work. Here's another little example for the activity code (as I don't know yours):
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.dbManager = new DatabaseManager(this);
setContentView(R.layout.main);
registerForContextMenu(getListView()); // catch clicks
if (!showList()) {
TextView tv = new TextView(this);
tv.setText(getString(R.string.txt_list_empty));
setContentView(tv);
}
}
private boolean showList() {
final Cursor c = dbManager.fetchListData();
startManagingCursor(c); // when the Activity finishes, the cursor is closed
if (!c.moveToFirst())
return false;
final SimpleCursorAdapter myAdapter = new SimpleCursorAdapter(
this, android.R.layout.simple_list_item_2, c,
new String[]{"name"} , new int[]{android.R.id.text1} );
setListAdapter(myAdapter);
return true;
}
Before spending a lot of time where you encounter problems, rather start where things are still working and take small steps for extensions. If you keep them simple, there's no great magic in using adapters.
You might try inserting a ViewBinder for debugging. You can use it to inspect which values are being passed for which views in the SimpleCursorAdaptor. Or just use it to manually do the binding yourself.
Something like this should work:
adapter.setViewBinder(new SimpleCursorBinder());
and then
public class SimpleCursorBinder implements SimpleCursorAdpater.ViewBinder {
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
/* set breakpoints and examine incoming data. */
// returning false, causes SimpleCursorAdapter to handing the binding
boolean bound = false;
String columnName = cursor.getColumnName(columnIndex);
TextView bindingView = null;
int viewId = view.getId();
// could just use this opportunity to manually bind
if (columnName.equals(SearchConstants._ID)) {
bindingView = (TextView)(viewId == R.id._id ? view : null);
} else if (columnName.equals(SearchConstants.NAME_COLUMN)) {
bindingView = (TextView)(viewId == R.id.name ? view : null);
} else if (columnName.equals(SearchConstants.SEARCH_COLUMN)) {
bindingView = (TextView)(viewId == R.id.search ? view : null);
}
if (bindingView != null) {
bindingView.setText(cursor.getString(columnIndex));
bound = true;
}
return bound;
}
}
It doesn't look like you've done anything wrong, You haven't shown all your code though so it might be difficult to spot any errors.
Not sure if the line startManagingCursor(c) does this for you but in my examples i have the following lines after my query has completed. Given your example looks absolutely fine it could be your cursor needs resetting to the first item.
(Ah, just noticed kichik pointed this out , but i'll leave my example.)
if (c != null) {
c.moveToFirst();
}
My queries often look like:
public Cursor getQueryCursor()
{
Cursor c;
c = null;
try{
c = myDataBase.query(TABLE_MYTABLE, new String[] {
COLUMN_ID,COLUMN_LABEL, COLUMN_TEXT},
null,
null,
null,
null,
null);
if (c != null) {
c.moveToFirst();
}
}catch(Exception ec)
{
Log.w("MY_APP", ec.getMessage());
}
return c;
}
Then when applying your query result i also often put try/catch statements around it and add break points at these points (sometimes the getMessage() returns null but other properties of the exception will highlight the issue. I also check the out put from LogCat. I've often been able to work out the root of my problems with the following.
try{
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.search, cursor, FROM, TO);
setListAdapter(adapter);
}catch(IllegalStateException e)
{
Log.w("MyApp", e.getMessage());
}catch(Exception es)
{
Log.w("MyApp", es.getMessage());
}
Please check your SQLite database, it must have a column '_id' as a primary key.
I had same kind of problem, and finally figure it out with this...