0

How do I create a neverending listview of list items with checkboxes that can be removed with a delete item button? The answer is below.

5
  • it's actually a null pointer on the isChecked method, but it is because my getTag method isn't working correctly. Commented Nov 19, 2013 at 20:16
  • When you call getListView().getTag(i), it's getting the Tag from the whole ListView, not the individual row, as I suspect you're trying to do. And, if you've not called ListView.setTag(), it will always return null. Commented Nov 20, 2013 at 2:33
  • I hadn't actually done a checked ListView yet, so I wrote a simple test app for multiple deletion. Lemme know if you want me to share some code or suggestions. Commented Nov 20, 2013 at 5:47
  • that would be great Mike. I am now going back over the tutorial at vogella.com/articles/AndroidListView/… we'll see if I can work this out by tomorrow. Commented Nov 20, 2013 at 13:35
  • I think I figured it out.. anyone who finds this and needs help can have some of my code if they email me at [email protected] Commented Nov 20, 2013 at 18:03

1 Answer 1

1

In order to create a neverending listview the first thing you need to have is a set of two runnables. These threads will update the array of data in your adapter.

final int itemsPerPage = 100;
ArrayList<HashMap<String,String>> listItems = new ArrayList<HashMap<String,String>>();
boolean loadingMore = false;
int item = 0;

//Since we cant update our UI from a thread this Runnable takes care of that!
 public Runnable returnRes = new Runnable() {

    @Override
    public void run() {
        //Loop thru the new items and add them to the adapter
        if(groceries.getGroceries().size() > 0){
                    for(int i=0;i < listItems.size();i++) {
                        HashMap<String,String> grocery = listItems.get(i);
                        adapter.add(grocery);
                    }
        //Update the Application title
                setTitle("Grocery List with " + String.valueOf(groceries.getGroceries().size()) + " items");
        //Tell to the adapter that changes have been made, this will cause the list to refresh
                    adapter.notifyDataSetChanged();
        //Done loading more.
                    loadingMore = false;
            }
    }
 };


//Runnable to load the items
public Runnable loadMoreListItems = new Runnable() {
@Override
public void run() {

    //Set flag so we cant load new items 2 at the same time
    loadingMore = true;
    //Reset the array that holds the new items
    listItems = new ArrayList<HashMap<String,String>>();
    //Get 8 new listitems
    for (int i = 0; i < itemsPerPage; i++) {
        if (i < groceries.getGroceries().size()) {
            listItems.add(groceries.getGroceries().get(i));
            item++;
        }
    }
    //Done! now continue on the UI thread
            runOnUiThread(returnRes);
}

};

Then your onCreate() method should look something like this with an array passed to your adapter:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_create_grocery_list);



    //add the footer before adding the adapter, else the footer will not load!
    View footerView = ((LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
            .inflate(R.layout.activity_footer_view, null, false);
    this.getListView().addFooterView(footerView);
    adapter = new ListViewAdapter(this,groceries);

    setListAdapter(adapter);
    getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
    //Here is where the magic happens
    this.getListView().setOnScrollListener(new OnScrollListener(){
        //useless here, skip!
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {}
        //dumdumdum
        @Override
        public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {
            //what is the bottom iten that is visible
            int lastInScreen = firstVisibleItem + visibleItemCount;
            //is the bottom item visible & not loading more already ? Load more !
            if((lastInScreen == totalItemCount) && !loadingMore && item < groceries.getGroceries().size()){
                Thread thread =  new Thread(null, loadMoreListItems);
                thread.start();
            }
        }
    });

}

You will also need a delete method to remove the items with checkboxes and a checkOff method as well. They look like this:

ArrayList<Integer> checkedBoxes = new ArrayList<Integer>();

ArrayList<HashMap<String,String>> checkedItems = new ArrayList<HashMap<String,String>>();

public void deleteItem(View view) {
      if (checkedBoxes.size() > 1 || checkedBoxes.size() == 0) {
          Toast.makeText(getApplicationContext(), "You can only delete one item at a time. Sorry :(", Toast.LENGTH_LONG).show();
          return;
      } else {

          checkedItems.add(groceries.getGroceries().get(checkedBoxes.get(0)));


          groceries.getGroceries().removeAll(checkedItems);
          checkedBoxes.clear();
          try {
              groceries.serialize();
          } catch (Exception e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
          }

          Intent intent = new Intent(getApplicationContext(),CreateGroceryList.class);
          startActivity(intent);

      }
}

public void checkOff(View view) {
    CheckBox box = (CheckBox)view;
    DataModel d = (DataModel)box.getTag();
    if(!checkedBoxes.contains(d.index)) {
        checkedBoxes.add(d.index);
    } else {
        checkedBoxes.remove((Integer)d.index);
    }
}

In order to communicate with the adapter it is helpful to have a DataModel class that will model our information. My DataModel has an index variable to keep track of the selected item.

public class DataModel {
int index;
HashMap<String,String> data;
boolean selected;

public DataModel(int i) {
    index = i;
    data = new HashMap<String,String>();
    selected = false;
}

public HashMap<String, String> getData() {
    return data;
}

public void setData(HashMap<String, String> data) {
    this.data = data;
}

public boolean isSelected() {
    return selected;
}

public void setSelected(boolean selected) {
    this.selected = selected;
}

}

Finally, here is the code for the BaseAdapter:

    public class ListViewAdapter extends BaseAdapter {//To create an adapter we have to extend BaseAdapter instead of Activity, or whatever.

    private ListActivity activity;
    private View vi;
    private ArrayList<DataModel> data;
    private static LayoutInflater inflater=null;


    public ListViewAdapter(ListActivity a, GroceryList g) {
        activity = a;
        data = new ArrayList<DataModel>();
        inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        groceries = g;
    }



    public void add(HashMap<String,String> a){
        DataModel d = new DataModel(data.size());
        d.setData(a);
        d.setSelected(false);
        data.add(d);
    }

    public ArrayList<DataModel> getData() {
        return data;
    }

    public int getCount() {   //get the number of elements in the listview
        return data.size();
    }

    public Object getItem(int position) {   //this method returns on Object by position
        return position;
    }

    public long getItemId(int position) {   //get item id by position
        return position;
    }

    public View getView() {
        return vi;
    }

    public View getView(int position, View convertView, ViewGroup parent) {   //getView method is the method which populates the listview with our personalized rows
        vi=convertView;
        final ViewHolder holder = new ViewHolder();
        if(convertView==null) {
            vi = inflater.inflate(R.layout.custom_row_view, null);

        //every item in listview uses xml "listview_row"'s design 

            holder.name = (CheckBox)vi.findViewById(R.id.name);
            holder.price = (TextView)vi.findViewById(R.id.price);   // You can enter anything you want, buttons, radiobuttons, images, etc.
            holder.quantity = (TextView)vi.findViewById(R.id.quantity);
            holder.name
            .setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

                @Override
                public void onCheckedChanged(CompoundButton buttonView,
                    boolean isChecked) {
                  DataModel element = (DataModel) holder.name
                      .getTag();
                  element.setSelected(buttonView.isChecked());

                }
              });
            vi.setTag(holder);
            holder.name.setTag(data.get(position));
            ViewHolder vholder = (ViewHolder) vi.getTag();
            vholder.name.setChecked(data.get(position).isSelected());
            HashMap<String, String> hash = new HashMap<String, String>();  //We need a HashMap to store our data for any item
            hash = data.get(position).getData();
            vholder.name.setText(hash.get("brand") + " " + hash.get("name"));  //We personalize our row's items.
            vholder.price.setText("$" + hash.get("price"));
            vholder.quantity.setText("Quantity: " + hash.get("quantity"));
        } else {
            vi = convertView;
            ((ViewHolder) vi.getTag()).name.setTag(data.get(position));
        }

            if (holder.name == null) {
                ViewHolder vholder = (ViewHolder) vi.getTag();
                vholder.name.setChecked(data.get(position).isSelected());
                HashMap<String, String> hash = new HashMap<String, String>();  //We need a HashMap to store our data for any item
                hash = data.get(position).getData();
                vholder.name.setText(hash.get("brand") + " " + hash.get("name"));  //We personalize our row's items.
                vholder.price.setText("$" + hash.get("price"));
                vholder.quantity.setText("Quantity: " + hash.get("quantity"));

            }   

        return vi;  
    }
}

class ViewHolder {
    CheckBox name;
    TextView price;
    TextView quantity;
    public CheckBox getName() {
        return name;
    }
    public void setName(CheckBox name) {
        this.name = name;
    }
    public TextView getPrice() {
        return price;
    }
    public void setPrice(TextView price) {
        this.price = price;
    }
    public TextView getQuantity() {
        return quantity;
    }
    public void setQuantity(TextView quantity) {
        this.quantity = quantity;
    }

}

You also need a few xml files in your layout folder this is what they will look like:

You need a footerview that will tell your list when to load new items:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:gravity="center_horizontal"
android:padding="3dp"
android:layout_height="fill_parent">
        <TextView
               android:id="@id/android:empty"
               android:layout_width="wrap_content"
               android:layout_height="fill_parent"
               android:gravity="center"
               android:padding="5dp"
               android:text="Add more grocery items..."/>

A custom row view that is populated by your BaseAdapter:

  <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/linearLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >



    <CheckBox
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="CheckBox"
        android:focusable="false"
        android:textSize="25dip"
        android:onClick="checkOff"
        />

    <TextView
        android:id="@+id/quantity"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="40dip"
        android:text="Lastname"
        android:textSize="15dip" />

    <TextView
        android:id="@+id/price"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="40dip"
        android:text="Lastname"
        android:textSize="15dip" />




</LinearLayout>

And a parent view, mine is called create_grocery_list because I'm writing a grocery list editor: This one must contain a ListView with the proper id.

    <?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" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="400dp" >

        <ListView
            android:id="@android:id/list"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1" >
        </ListView>

    </LinearLayout>

    <AbsoluteLayout
        android:layout_width="match_parent"
        android:layout_height="72dp" >

        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_x="105dp"
            android:layout_y="0dp"
            android:onClick="deleteItem"
            android:text="@string/deleteItem" />

        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_x="8dp"
            android:layout_y="0dp"
            android:onClick="goToAddItemScreen"
            android:text="@string/addItem" />

        <Button
            android:id="@+id/button3"
            style="?android:attr/buttonStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_x="221dp"
            android:layout_y="0dp"
            android:onClick="scanner"
            android:text="@string/scanCode" />
    </AbsoluteLayout>

</LinearLayout>

And that's about it... hope this helps anyone. It's the most complete tutorial you'll find.

I learned all this from this tutorial: http://www.vogella.com/articles/AndroidListView/article.html#androidlists_overview then added the two runnables to make a neverending grocery list :) have fun programming...

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.