Is is possible to add additional column to SimpleCursorAdapter - android

I have successfully used a SimpleCursorAdapter to list displayname and value (both in my DB) and displayed them in my list activity.
What I am was trying to do now was add a new view (ImageView) for each item in my list activity. It is supposed to look like this in the end.
Image_1_NotInDB -- DisplayName1FromDB -- DisplayName1FromDB
Image_2_NotInDB -- DisplayName2FromDB -- DisplayName2FromDB.
The image is going to different (based on DisplayName1FromDB). I don't think SimpleCursorAdapter is good anymore for this purpose.
I tried creating a customSimpleCursorAdapter extending SimpleCursorAdapter and tried to use 'newView' and 'bindView' methods to achieve. I pretty much followed this: Custom CursorAdapters.
The problem is; what Image I use is based on a value from DB (which I intended to pass in the constructor or customSimpleCursorAdapter)
public View newView(Context pContext, Cursor pCursor, ViewGroup pParent)
{
Cursor lCursor = getCursor();
final LayoutInflater inflater = LayoutInflater.from(pContext);
View lView = inflater.inflate(layout, pParent, false);
int lImage = "dog".equals(variable) ? R.drawable.dog : R.drawable.cat;
// "variable" is a member variable (set at the constructor)
ImageView lImageView = (ImageView) lView.findViewById(R.id.appImage);
if (lImageView != null)
{
lImageView.setImageResource(lImage);
}
return pParent;
}
public void bindView(View pView, Context pContext, Cursor pCursor)
{
int lImage = "dog".equals(variable) ? R.drawable.dog : R.drawable.cat;
// "variable" is a member variable (set at the constructor)
ImageView lImageView = (ImageView) lView.findViewById(R.id.appImage);
if (lImageView != null)
{
lImageView.setImageResource(lImage);
}
}
This is how I tried using the "customSimpleCursorAdapter"
String[] lDisplay = new String[] {KEY_NAME, KEY_TIME};
int[] lValues = new int[] {R.id.name, R.id.time};
CustomRowCursorAdapter lCursorAdapter = new CustomRowCursorAdapter(this, R.layout.row, lSTime, lDisplay, lValues, "MY IDEA WAS TO PASS THE ANIMAL NAME HERE, BUT NOT LUCK as I am not sure How ");
lCursorAdapter.newView(this, lSTime, getListView());
lCursorAdapter.bindView(getListView(), this, lSTime);
setListAdapter(lCursorAdapter);
Is ArrayAdapter the answer? If yes, could you share what parameters would you pass to it?

I recommend to go for custom adapter

Is ArrayAdapter the answer?
No you can not use the ArrayAdapter here. Because you need to display data from the database therefore you must use here a Custom CursorAdapter.

You could add an additional field to the projection from the database.
Here is an example of a projection I have used to format a cost based off of the a price of an item (stored in cents) and an associated unit.
public static final String CONCATE_COST
= "'$' || CASE WHEN SUBSTR(ROUND("+COLUMN_PRICE
+"/100.0, 2), LENGTH(ROUND("+COLUMN_PRICE
+"/100.0, 2))-1, 1)='.' THEN ROUND("+COLUMN_PRICE
+"/100.0, 2) || '0' else ROUND("+COLUMN_PRICE
+"/100.0, 2) end || "+COLUMN_UNIT;
Use that as a column and you can pair it with a field in your view using the normal mapping of column to R.id.*.

Related

AutoCompleteTextView with SimpleCursorAdapter does not filter

In my app, I have a few AutoCompleteTextView widgets that use an ArrayAdapter.
private List<String> adapterList = new ArrayList<String>();
ArrayAdapter<String> dropdownAdapter;
dropdownAdapter = new ArrayAdapter<>(getContext(), R.layout.simple_dropdown_item, adapterList);
autoCompleteTextView.setAdapter(dropdownAdapter);
It works beautifully. As I type into the View, I get words-starting-with results in the dropdown.
I want to do this with another AutoCompleteTextView, but this time using a SimpleCursorAdapter.
nameSearchCursor = dbHelper.getChecklistTabDataByChecklistId(outingId, checklistId, nameColumn);
NameSearch = root.findViewById(R.id.SearchNames);
String[] nsColumns = new String[]{nameColumn};
int[] nsTo = new int[]{R.id.simpleDropdownItem};
nameSearchCursorAdapter = new SimpleCursorAdapter(getContext(), R.layout.simple_dropdown_item,
nameSearchCursor, nsColumns, nsTo, 0);
NameSearch.setAdapter(nameSearchCursorAdapter);
If I start typing in this new View, the dropdown appears and shows the entire list, and nothing changes as I type. No filtering occurs. What do I need to do differently (and perhaps why) to get a CursorAdapter to work with this View that I didn't need to do when using an ArrayAdapter. I have searched this site and read the Developer Docs and there must be something I just don't get. Please enlighten me.
This site allowed me to answer this question: http://www.outofwhatbox.com/blog/2010/11/android-simpler-autocompletetextview-with-simplecursoradapter/
Here is my completed code:
private void setUpNameSearch() {
// Get AutoCompleteTextView
nameSearchView = root.findViewById(R.id.SearchNames);
// Define from/to info
final String[] nsColumns = new String[]{nameColumn};
final int[] nsTo = new int[]{R.id.simpleDropdownItem};
// Create adapter. Cursor set in setFilterQueryProvider() below.
nameSearchCursorAdapter = new SimpleCursorAdapter(getContext(), R.layout.simple_dropdown_item,
null, nsColumns, nsTo, 0);
// Set adapter on view.
nameSearchView.setAdapter(nameSearchCursorAdapter);
// OnItemClickListener - User selected value from DropDown
nameSearchView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> listView, View view, int position, long id) {
// Get the cursor. Positioned to the corresponding row in the result set.
Cursor cursor = (Cursor) listView.getItemAtPosition(position);
// Get the name selected
String selectedName = cursor.getString(cursor.getColumnIndexOrThrow(nameColumn));
// Do something with this value...
}
});
// Set the CursorToStringconverter, to provide the values for the choices to be displayed
// in the AutoCompleteTextview.
nameSearchCursorAdapter.setCursorToStringConverter(new SimpleCursorAdapter.CursorToStringConverter() {
#Override
public CharSequence convertToString(Cursor cursor) {
final String name = cursor.getString(cursor.getColumnIndexOrThrow(nameColumn));
return name;
}
});
// Set the FilterQueryProvider, to run queries for choices
nameSearchCursorAdapter.setFilterQueryProvider(new FilterQueryProvider() {
#Override
public Cursor runQuery(CharSequence constraint) {
Cursor cursor = dbHelper.getMatchingNames(outingId, checklistId, nameColumn,
(constraint != null ? constraint.toString() : null));
return cursor;
}
});
}
I wanted to duplicate the word-starting-with default functionality of the AutoCompeteTextView using the SQLite Cursor, only to find that REGEXP are not fully supported. So this StackOverflow topic gave me the LIKE workaround. SQLite LIKE alternative for REGEXP, Match Start of Any Word
I hope this helps others.

Android database - curser and adapter

Quite new to Android development, can manage the more simple tasks but never dealt with databases before. I have managed to get so far, however, when trying to output all results from the databases, which has been created, i am only receiving references to the record not the actual values. Debugging shows that the values are being assigned correctly, however, are showing the object reference and not the values them selves. What i see is a list similar to:
com.example.testdb.myapplication.Films#42ff37f0
Please see below for my code:
// Getting All Films
public List<Films> getAllFilms() {
List<Films> filmList = new ArrayList<Films>();
// Select All Query
String selectQuery = "SELECT * FROM film_table";
SQLiteDatabase db = new MyDBHandler(this).getWritableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
ArrayAdapter<Films> adapter = new ArrayAdapter<Films>(MainActivity.this, android.R.layout.simple_list_item_1,filmList);
// looping through all rows and adding to list
if (cursor.moveToFirst()) {
do {
Films film = new Films();
film.setID(Integer.parseInt(cursor.getString(0)));
film.setfilmName(cursor.getString(1));
film.setFilmActors(cursor.getString(2));
film.setFilmDirectors(cursor.getString(3));
film.setfilmDescription(cursor.getString(4));
film.setfilmFave(cursor.getString(5));
ListView listView = (ListView) findViewById(android.R.id.list);
listView.setAdapter(adapter);
filmList.add(film);
} while (cursor.moveToNext());
}
// return Film list
return filmList;
}
I would very much appreciate any input which may guide me in the right direction. I have tried many methods to no avail.
Thank you in advance.
Okay, I have to guess a little bit here because you don't have more code posted.
You say you have a list that is showing the output of the default toString() method for your Films object.
I am guessing your list adapter is using the default getView() method, since the default behavior is to print the toString() output for the item.
If you want to see the database values of your Films object formatted into a list, you have to extend a list adapter class like BaseAdapter or create a class that implements ListAdapter, then override the getView() method to create a View that contains your values.
Have a look at ListAdapter.getView() and see if that will solve your issue. If not, you'll have to post more code and be more specific about what's happening.
You are setting the adapter before you are retrieving the values from database, hence there would be no data in the adapter. Moved setting the adapter after retrieving values from DB and after setting them in Films object.
public List<Films> getAllFilms() {
List<Films> filmList = new ArrayList<Films>();
// Select All Query
String selectQuery = "SELECT * FROM film_table";
SQLiteDatabase db = new MyDBHandler(this).getWritableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
// looping through all rows and adding to list
if (cursor.moveToFirst()) {
do {
Films film = new Films();
film.setID(Integer.parseInt(cursor.getString(0)));
film.setfilmName(cursor.getString(1));
film.setFilmActors(cursor.getString(2));
film.setFilmDirectors(cursor.getString(3));
film.setfilmDescription(cursor.getString(4));
film.setfilmFave(cursor.getString(5));
filmList.add(film);
} while (cursor.moveToNext());
}
ArrayAdapter<Films> adapter = new ArrayAdapter<Films>(MainActivity.this, android.R.layout.simple_list_item_1,filmList);
ListView listView = (ListView) findViewById(android.R.id.list);
listView.setAdapter(adapter);
return filmList;
}
Also add a custom adapter to display the value from the class Film:
public class MyClassAdapter extends ArrayAdapter<Films> {
private static class ViewHolder {
private TextView filmname;
private TextView actors;
................
}
public MyClassAdapter(Context context, int textViewResourceId, ArrayList<Films> items) {
super(context, textViewResourceId, items);
}
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(this.getContext())
.inflate(R.layout.film_layout, parent, false);
viewHolder = new ViewHolder();
viewHolder.itemView = (TextView) convertView.findViewById(R.id.ItemView);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
Films item = getItem(position);
if (item!= null) {
// My layout has only one TextView
// do whatever you want with your string and long
viewHolder.itemView.setText(String.format("%s", item.getfilname));
}
return view;
}
}
Layout for the listrow(film_layout.xml)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="#+id/filname"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
.................... //Form how each row should be displayed
</RelativeLayout>
Tutorial how to create a custom listview here
Sorry i didn't post sooner. In the end i fixed the issue using the following code, which outputs into the list similar to you answer, however, without the floater element...
String[] columnName = new String[]
{MyDBHandler.COLUMN_NAME, MyDBHandler.COLUMN_ACTORS, MyDBHandler.COLUMN_DIRECTORS,MyDBHandler.COLUMN_DESCRIPTION, MyDBHandler.COLUMN_FAVE};
int[] displayName = new int[]
{R.id.filmNameEdit, R.id.filmActors, R.id.filmDirectors, R.id.filmDescription, R.id.filmFave};
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.activity_listview,cursor, columnName, displayName);
ListView listView = (ListView) findViewById(android.R.id.list);
listView.setAdapter(adapter);
Thanks for all the suggestions. This turned out to be the most simple answer for me.

Setting an image in ListView item using SimpleCursorAdapter and ViewBinder

Is there a way I could set an image to an ImageView inside a ListView? In this, I am using a SimpleCursorAdapter to display all the fields and using a ViewBinder to set the image bitmap to the ImageView. The image is downloaded using an AsyncTask. The code that I have written is provided below:
private void updateTimelineUI() {
Cursor data = dbHelper.query(Constants.TABLE_NAME, null, null);
if (data.moveToFirst()) {
adapter = new SimpleCursorAdapter(this, R.layout.tweet_row, data, new String[] {Constants.CREATED_TIME, Constants.USERNAME, Constants.PROFILE_IMAGE, Constants.TWEET}, new int[] {R.id.time, R.id.username, R.id.userImageView, R.id.tweetMessage});
SimpleCursorAdapter.ViewBinder viewBinder = new SimpleCursorAdapter.ViewBinder() {
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
if(view != null && view.getId() != R.id.userImageView) {
return false;
}
String imageUrlString = cursor.getString(columnIndex);
String username = cursor.getString(cursor.getColumnIndex(Constants.USERNAME));
String path = getDir("images", MODE_PRIVATE).getAbsolutePath() + "/" + username + ".png";
ImageDownloader downloader = new ImageDownloader();
downloader.setImageUrlString(imageUrlString);
downloader.setImageView(view);
downloader.setFilePath(path);
downloader.execute(imageUrlString);
return true;
}
};
int index = data.getColumnIndex(Constants.PROFILE_IMAGE);
//Log.d(Constants.TAG, "" + index);
adapter.setViewBinder(viewBinder);
viewBinder.setViewValue(userImageView, data, index);
timelineList.setAdapter(adapter);
}
}
Is there a way to set the image to the correct row with this method?
With the code that I currently have, the images are downloaded successfully. However, the images are being set randomly.
You should make your own adapter which shall inherit from SimpleCursorAdapter and therefore you can make your own ListView item with your custom design and set a custom layout for it in which you can add whatever Ui element that you want including ImageView
I created a custom class that inherits from SimpleCursorAdapter, like Serdar Dogruyol mentions. Inside this custom class I #Override bindView. Inside bindView I call the super, then get a handle to my ImageView using view.findViewById, with view being one of the parameters passed into bindView.

Populating a listview with images from the SD card (not a set amount of items in list)

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);

checkbox in List being checked unexpectedly

I have list of checkboxes in list binded by Custom simpleCursorAdapter.
In my custom simpleCursorAdapter, I've overridden newView and bindView with my modifications.
I've managed somehow to do multichoice.
The wierd thing is, after I delete any item from my list, the first item's checkbox is being checked all of a sudden. How does that happen? How can I solve it?
My SimpleCursorAdapter class:
public class MyListCursorAdapter extends SimpleCursorAdapter
{
private Context context;
private int layout;
public MyCursorAdapter(Context context, int layout, Cursor c,
String[] from, int[] to)
{
super(context, layout, c, from, to);
this.context = context;
this.layout = layout;
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent)
{
Cursor c = getCursor();
final LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(layout, parent, false);
CheckBox chkBoxBtn = (CheckBox) v.findViewById (R.id.deleteTwittChkBox);
if (chkBoxBtn != null)
{
chkBoxBtn.setChecked(false);
}
return v;
}
#Override
public void bindView(View v, Context context, Cursor c)
{
--binding view to my textsview in my items
//now it's the importat part:
CheckBox chkBoxBtn = (CheckBox) v.findViewById(R.id.deleteTwittChkBox);
if (chkBoxBtn != null)
{
chkBoxBtn.setId(Integer.valueOf(c.getString(c
.getColumnIndex(MyUsers.User._ID))));
chkBoxBtn.setOnClickListener(new OnItemClickListener(chkBoxBtn, v));
chkBoxBtn.setChecked(false);
}
}
//i couldnt find another way of doing this, but this is how i set listeners to my checkboxses
static ArrayList<String> checkedItemsList = new ArrayList<String>();
private class OnItemClickListener implements OnClickListener
{
private int mPosition;
private CheckBox chkBox;
OnItemClickListener(CheckBox mChkBox, View v)
{
chkBox = mChkBox;
chkBox.setChecked(false);
}
#Override
public void onClick(View v)
{
if (chkBox.isChecked())
{
checkedItemsList.add(String.valueOf(chkBox.getId()));
}
else
{
checkedItemsList.remove(String.valueOf(chkBox.getId()));
}
}
}
}
Here is the code part from the ListActivity class which describes the button that deletes the checked box items:
OnClickListener btListener = new OnClickListener()
{
public void onClick(View view)
{
// long[] items = listView.getCheckItemIds();
int x = 0;
Uri myUri = Uri
.parse("content://com.idan.datastorageprovider/users");
String where = "_id" + "=?";
//here i am tatking all checkboxes which ive added from the adapter class
ArrayList<String> checkedItemsList = MySimpleCursorAdapter.checkedItemsList;
for (String itemID : checkedItemsList)
{
getContentResolver()
.delete(myUri, where, new String[] { itemID});
checkedItemsList.remove(itemID);
}
}
};
I doubt that SimpleCursorAdapter is the right class to extend here.
Is the "checked" state connected to the data XML in any way? No? So you need your own custom adapter!
Basically all adapters have to implement a way to generate a view from a given element (more precisely an element position!). This will be called at any time where the list wants to display an element. Now, the trick it uses is to re-use formerly created list view elements that cannot be seen on screen any more! Thus: when you scroll your list down and an element disappears at the top, EXACTLY this view object will be re-used for the next appearing item.
So, when this method is called with a given "old" view that should be re-used, all contained elements will have to be set according the elements data. If a checkbox is part of this game, you will have to have a storage for the checked state! It is not sufficient to have a checkbox as there will be less checkbox objects as there are list elements!
SimpleCursorAdapters are there to - yeah - represent SIMPLE things. An XML describing data (images and text, as the documentation states). Because of this simplicity all you have to do here is provide a method to create NEW element view objects - you are not intercepting the re-use process AT ALL! It basically only knows how to put the data into an existing view object - but it is lacking the knowledge of how to handle checked/unchecked boxes!
Your solution: write your own BaseAdapter extension and do what has to be done: implement "getView" (and some other methods like getItem, getItemId and getCount). It's not hard at all!
This API Demo uses a BaseAdapter and the mExpanded state here is basically identical to your checkbox states!
Good luck!
You might need to call notifyDataSetChanged when you modify the data.
The problem is probably that you're calling setChecked from within the onItemClickListener. One hacky way around this is to do the following before and after you call setChecked from within your listener:
chkBox.setClickable(false);
chkBox.setChecked(false);
checkBox.setClickable(true);
This will prevent your onItemClickListener from getting called when you manually call setChecked.

Categories

Resources