Search code examples
androidandroid-cursoradapter

CustomAdapter with different row types explanations


I tryied on Stackoverflow to post my code, but I think there are too many mistake in this to be fixed.

So as my biggest problem is a misunderstanding, I'll just ask for the way I have to develop it.

PROBLEM : I want to do a listView, feeded by a custom cursorAdapter.

Why custom cursorAdapter ?

Because for now, I want to have an editText on the first row of the listView, then items from database, then an other editText at the very last row of the listView. I could solve it with headers and footers, but later I want many other row template in the same listView, and maybe even many cursor, that's the reason.

I tryied to use many types in a adapter following this tutorial : Link about having many row types in one listView

Of course I overrided bindView and newView.

BUT

It's failing. I have many bugs like I don't know how to directly add both editText to my listView, etc ... The biggest problem for me is comprehension, I just need an explanation of how to do that, I mean what exactly should I do in bindView, and what exactly should I do in newView.

Ok I changed my mind, here is the code of my adapter. Watch out, it's maybe horrible.

    package com.android.activity;


import android.content.Context;
import android.database.Cursor;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.EditText;
import android.widget.TextView;

public class NotesCursorAdapter extends CursorAdapter{

    private Context context;
    //  private Cursor cursor;
    private int addNoteTopPosition = 0;
    private int addNoteBottomPosition;
    private LayoutInflater inflater;

    private static final int TYPE_ITEM = 0;
    private static final int TYPE_ADD_NOTE_BOTTOM = 1;
    private static final int TYPE_ADD_NOTE_TOP = 2;
    private static final int TYPE_MAX_COUNT = TYPE_ADD_NOTE_TOP + 1;

    public NotesCursorAdapter (Context context, Cursor cursor, int flag){
        super(context, cursor);
        this.context = context;
        addNoteTopPosition = 0;
        addNoteBottomPosition = cursor.getCount()+1;

        inflater = LayoutInflater.from(this.context);

    }

    //  public 

    @Override
    public int getCount() {  
        return super.getCount() + 2;
    } 





    @Override
    public View getView(int position, View convertView, ViewGroup parent){
        ViewHolder holder = null;
        int type = getItemViewType(position);
        if (convertView == null) {
            holder = new ViewHolder();
            switch (type) {

            case TYPE_ADD_NOTE_TOP:
                convertView = inflater.inflate(R.layout.add_note_top, null);
                holder.view = (EditText)convertView.findViewById(R.id.add_note_top_id);
                break;
            case TYPE_ITEM:
                convertView = inflater.inflate(R.layout.row_note, null);
                holder.view = (TextView)convertView.findViewById(R.id.note);
                getCursor().moveToPosition(position - 1);
                //              System.out.println("modified : " + getCursor().getInt(getCursor().getColumnIndex("_id")));
                ((TextView) holder.view).setText(getCursor().getInt(getCursor().getColumnIndex("_id")) + getCursor().getString(getCursor().getColumnIndex("content_note")));
                break;
            case TYPE_ADD_NOTE_BOTTOM:
                convertView = inflater.inflate(R.layout.add_note_bottom, null);
                holder.view = (EditText)convertView.findViewById(R.id.add_note_bottom_id);
                break;
            }
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder)convertView.getTag();
        }

        return convertView;
    }

    @Override
    public int getItemViewType(int position) {
        int type;
        if (position == addNoteTopPosition){
            type = TYPE_ADD_NOTE_TOP;
        } else if (position == addNoteBottomPosition){
            type = TYPE_ADD_NOTE_BOTTOM;
        }else {
            type = TYPE_ITEM;
        }
        return type;
    }

    @Override
    public int getViewTypeCount() {
        return TYPE_MAX_COUNT;
    }

    @Override
    public long getItemId(int position) {  
        return position;  
    }

    public static class ViewHolder {
        public View view;
    }

}

Note : I'll fix later some optimisation like viewHolder, etc ...

Note 2 : Just in case, here is my main activity code

package com.android.activity;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewDebug.FlagToString;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.CursorAdapter;

import com.android.database.Note;
import com.android.database.NoteDataSource;

public class EverydayNotesAndroid3Activity extends Activity {
    /** Called when the activity is first created. */

    private Cursor cursorNotes;
    private NotesCursorAdapter notesCursorAdapter;
    private InputMethodManager imm;
    private Activity activity;
    private ListView listView;
    private int positionItem;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        activity = this;

        NoteDataSource.getSingletonObject(activity);

        NoteDataSource.getSingletonObject(activity).open();

        cursorNotes = NoteDataSource.getSingletonObject(activity).getCursorUpdatedDatabase(); // return all notes in a cursor

        startManagingCursor(cursorNotes);

        listView = (ListView) findViewById(R.id.main_list);

        notesCursorAdapter = new NotesCursorAdapter(activity, cursorNotes, 3);

        listView.setAdapter(notesCursorAdapter);

        Button b = new Button(activity);

        b = (Button) findViewById(R.id.done);

        b.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {

                NoteDataSource.getSingletonObject(activity).createNoteTop("Hello stack overflow world");
                cursorNotes = NoteDataSource.getSingletonObject(activity).getCursorUpdatedDatabase();
                notesCursorAdapter.changeCursor(cursorNotes); 
                notesCursorAdapter.notifyDataSetChanged();
            }

        });



        listView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View v, int position, long id){
                if (position == 0){
                    showAlertWindowAddTop(parent, v, position, id);

                }else if (position == parent.getChildCount()-1){

                    showAlertWindowAddBottom(parent, v, position, id);
                }else{

                    showAlertWindowModify(parent, v, position, id);

                }

            }
        });

    }

And don't worry about :

1) functions showAlertWindowblabla() ==> it works

2) the createNoteTop function works too, it insert a row on database.

Thanks for reading, looking for your answers.

EDIT : Ok so after some looooooong work for not very much, I'm getting closer of what I want. Unfortunately I still have a bug of display, when I modify a field, I don't know why, he just drop this field somewhere else in the list. I think it's a big mistake (on bindView or newView method) easily fixable, but I'm tired and I don't find where it comes from. So to kill two birds with one stone, I post modified code of my customAdapter.


Solution

  • Its quite common to subclass CursorAdapter since its meant to be done that way. You would use a ResourceCursorAdapter, etc in case you have simple needs.

    So just a bit of background: ListView recycles views. Therefore newView is only called for (number of items on the screen) + 1 (the one that is partially visible as you start scrolling). Then these items get reused as they scroll off the screen. Therefore, its pretty much useless to set the text labels in newView as it will anyway be overridden in bindView(which is the place where you should do that), as newView will not be called for every TYPE_ITEM.

    So I guess the main problem is that you are changing the contents of the list view by modifying addNoteBottomPosition in your newView method which is the worst thing you can do, really. A list adapter is meant to produce stable results. That means if you say that getItemViewType(8) was of type TYPE_ADD_NOTE_BOTTOM, you cannot turn around next time and and say "yay, now its TYPE_ITEM". Which is what you are doing. Your code relies on the fact that newView is called in a certain order. The other problem is that you are messing around with positions which CursorAdapter relies upon.

    So how do you solve this?

    There are many approaches, I think you used probably the more complicated one. The simplest approach is to

    Use a ResourceCursorAdapter for the items only, and use ListView.addHeaderView() and ListView.addFooterView() for adding headers and footers. If you check out the source code of ListView then you will notice that addHeaderView will in fact use a HeaderViewListAdapter (http://developer.android.com/reference/android/widget/HeaderViewListAdapter.html) which is basically a wrapper list adapter which wraps around the nested ListAdapter that you would then provide.

    With this approach you don't need to worry about different view types, etc.

    In case I couldn't convince you and you still want to subclass CursorAdapter then you need to review its implementation. You will notice that it relies on correct positions and therefore, if you would really want to mess with positions and have them separate from cursor row positions, then you would need to override all sorts of methods such as getItem(), getItemId().

    Long story short, you don't want to go there.