ExpandableListAdapter onClickListener, call notifyDataSetChanged()
Asked Answered
C

5

13

I have a custom adapter that uses an onclickListener to change the parent text, I can't figure out how to get the notifyDataSetChanged method to work within the adapter.

what should happen is I have an expandable view, and when you click on the button that is within the child it updates the parent with the text of the child...

Here's a picture example of what SHOULD happen. before clicking button USE THIS: enter image description here

and After clicking button USE THIS: enter image description here

"Choose an Ingredient" changes to "Rice, cups" or whatever is clicked on

so in my code shown, after clicking the button it should update parent, which it does, then refresh the view which I can't do for some reason?

heres my code, Thanks in advance!

    package com.example.andrew.mypantry;

import android.content.Context; 
import android.graphics.Typeface;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import org.w3c.dom.Text;

import java.util.HashMap;
import java.util.List;

/**
 * Created by andrew on 7/1/2015.
 */
public class IngExpandableListAdapter extends BaseExpandableListAdapter {

private Context context;
private List<String> listDataHeader; //header titles
//child data in format of <header title, child title>
private HashMap<String, List<String>> listDataChild;

public IngExpandableListAdapter(Context context, List<String> listDataHeader,
                                HashMap<String, List<String>> listDataChild) {
    this.context = context;
    this.listDataHeader = listDataHeader;
    this.listDataChild = listDataChild;
}

@Override
public Object getChild(int groupPosition, int childPosition) {
    return this.listDataChild.get(this.listDataHeader.get(groupPosition)).get(childPosition);
}

@Override
public long getChildId(int groupPosition, int childPosition) {
    return childPosition;
}

private void test() {


    //notifyDataSetChanged();
    //super.notifyDataSetChanged();
    //this.notifyDataSetChanged();
}
@Override
public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild,
                         View convertView, ViewGroup parent) {
    final String childText = (String) getChild(groupPosition,childPosition);

    if (convertView == null) {
        LayoutInflater layoutInflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = layoutInflater.inflate(R.layout.ingredient_list_item, null);
    }

    TextView txtListChild = (TextView) convertView.findViewById(R.id.ingredientContentTextView);

    txtListChild.setText(childText);

    //handle button
    Button ingredientButton = (Button)convertView.findViewById(R.id.useIngredient_button);
    ingredientButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //change header to ingredient selected
            listDataHeader.set(groupPosition, childText);
            //test();
        }
    });

    return convertView;
}

@Override
public int getChildrenCount(int groupPosition) {
    return this.listDataChild.get(this.listDataHeader.get(groupPosition)).size();
}

@Override
public Object getGroup(int groupPostion) {
    return this.listDataHeader.get(groupPostion);
}

@Override
public int getGroupCount() {
    return this.listDataHeader.size();
}

@Override
public long getGroupId(int groupPosition) {
    return groupPosition;
}

@Override
public View getGroupView(int groupPosition, boolean isExpanded,
                         View convertView, ViewGroup parent) {

    String headerTitle = (String) getGroup(groupPosition);

    if(convertView == null) {
        LayoutInflater layoutInflater = (LayoutInflater) this.context.getSystemService(context.LAYOUT_INFLATER_SERVICE);
        convertView = layoutInflater.inflate(R.layout.ingredient_list_group, null);
    }

    //handle textView
    final TextView listHeader = (TextView) convertView.findViewById(R.id.ingredientGroupTextView);
    listHeader.setTypeface(null, Typeface.BOLD);
    listHeader.setText(headerTitle);

    //handle button
    Button deleteButton = (Button)convertView.findViewById(R.id.deleteButton);
    deleteButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //remove item from list
            Log.d("TESTING", "item should be removed");

        }
    });


    return convertView;
}

@Override
public boolean hasStableIds() {
    return false;
}

@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
    return true;
}

}

Columnar answered 3/7, 2015 at 18:23 Comment(5)
Could you define your question a little bit more? What is happening that shouldn't and what line is your code crashing on?Oxcart
The "notifyDataSet" causes the crash with it commented out it runs through everything fine. I'm not sure why it crashes is the main problemColumnar
Can you post the logcat?Oxcart
can you show the type you are using in listDataHeader ...??Rhymester
Can you post all your adapter code?Nippers
S
3

I believe the issue is in the data structures you use. You keep list of children assigned to the parent's name in a hash table. When parent's name changes in the list of parents, old children assignment in the hash table is not valid anymore.

I would suggest you to use SparseArray<List<String>> or List<List<String>> for listDataChild field. It will map group position to the list of children directly. Thus changing parent's name in listDataHeader wont break data consistency, as the group position will stay the same. Now you should be able to safely use notifyDataSetChanged() method to update headers.

Hope this helps.

Shirberg answered 16/7, 2015 at 5:59 Comment(2)
Pretty sure this is it! makes sense now why it is was crashing.Columnar
I agree, and I also posted an answer explaining the crash you are getting and what causing it.Nullify
N
4

As beworker mentioned, your problem comes from using wrong data structure and here is why your app crashes. You are using header titles as keys to your hashmap and when you modify a title in listDataHeader the modification is correctly applies to it but not to the key object inside the hashmap. Meaning that your children still exist under the old title key inside the hashmap and the new title key has null as its value. Thus you will get a NullPointerException when you call the

this.listDataChild.get(this.listDataHeader.get(groupPosition)).get(childPosition);

since

this.listDataChild.get(this.listDataHeader.get(groupPosition))

returns Null

Solution would be using ArrayList for header titles and ArrayList> for children. The key in both of these arrayLists is your group index.

Nullify answered 16/7, 2015 at 16:4 Comment(0)
S
3

I believe the issue is in the data structures you use. You keep list of children assigned to the parent's name in a hash table. When parent's name changes in the list of parents, old children assignment in the hash table is not valid anymore.

I would suggest you to use SparseArray<List<String>> or List<List<String>> for listDataChild field. It will map group position to the list of children directly. Thus changing parent's name in listDataHeader wont break data consistency, as the group position will stay the same. Now you should be able to safely use notifyDataSetChanged() method to update headers.

Hope this helps.

Shirberg answered 16/7, 2015 at 5:59 Comment(2)
Pretty sure this is it! makes sense now why it is was crashing.Columnar
I agree, and I also posted an answer explaining the crash you are getting and what causing it.Nullify
T
0

Instead of using notifyDataSetChanged(); create an interface from where data is coming and implement it is your adapter and then change the value of the parent in that interface function, as getChildView() is called after every child it will update your listview Parent. Checked and Verified as I have same problem

Say you want to transfer data from Adapter to Adapter

adapter.java

public interface data { public void dataMethod(String data); }

and implement this method in adapter

public void dataMethod(String data) { this.data = data; }

call dataMethod() from getChildView() and update string as I have shown

and set data in getViewChild method in adapter

Tricuspid answered 3/7, 2015 at 18:56 Comment(4)
I don't understand... Example?Columnar
I'm not passing information from main, it's all in the adapter. I clarified my question a little to help. But I need some way updating the adapter from within the adapter, or making an onclicklistener outside the adapter?Columnar
I still don't think this is my question, or I'm not getting the answer. Ok maybe I should use updating. I am successfully changing the parent information. However the View isn't refreshing. Typically outside the adapter you can call Adapter.notifyDataSetChanged(); and it refreshes. If you call notifyDataSetChanged() within the adapter it crashes.Columnar
Can you post the logcat from the crash?Oxcart
G
0

As @beworker already mentioned, you shouldn't store children in a HashMap. Use an ArrayList instead and access children by a group index. That's how you can change the adapter. As a bonus I've implemented group removal.

public class IngExpandableListAdapter extends BaseExpandableListAdapter {
    private Context context;
    private List<String> listDataHeader; //header titles
    private List<List<String>> listDataChild;

    public IngExpandableListAdapter(Context context, List<String> listDataHeader,
                                    HashMap<String, List<String>> listDataChild) {
        this.context = context;
        this.listDataHeader = listDataHeader;
        this.listDataChild = new ArrayList<List<String>>();
        for (String header : listDataHeader) {
            List<String> children = listDataChild.get(header);
            if (children == null) {
                children = Collections.emptyList();
            }
            this.listDataChild.add(children);
        }
    }

    @Override
    public Object getChild(int groupPosition, int childPosition) {
        return this.listDataChild.get(groupPosition).get(childPosition);
    }

    @Override
    public long getChildId(int groupPosition, int childPosition) {
        return childPosition;
    }

    @Override
    public View getChildView(final int groupPosition, final int childPosition,
                             boolean isLastChild, View convertView,
                             ViewGroup parent) {
        final String childText = (String) getChild(groupPosition, childPosition);

        if (convertView == null) {
            LayoutInflater layoutInflater = LayoutInflater.from(this.context);
            convertView =
                    layoutInflater.inflate(R.layout.ingredient_list_item, null);
        }

        TextView txtListChild =
                (TextView) convertView.findViewById(R.id.ingredientContentTextView);
        txtListChild.setText(childText);

        //handle button
        Button ingredientButton =
                (Button) convertView.findViewById(R.id.useIngredient_button);
        ingredientButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //change header to ingredient selected
                listDataHeader.set(groupPosition, childText);
                notifyDataSetChanged();
            }
        });

        return convertView;
    }

    @Override
    public int getChildrenCount(int groupPosition) {
        return this.listDataChild.get(groupPosition).size();
    }

    @Override
    public Object getGroup(int groupPostion) {
        return this.listDataHeader.get(groupPostion);
    }

    @Override
    public int getGroupCount() {
        return this.listDataHeader.size();
    }

    @Override
    public long getGroupId(int groupPosition) {
        return groupPosition;
    }

    @Override
    public View getGroupView(int groupPosition, boolean isExpanded,
                             View convertView, ViewGroup parent) {

        String headerTitle = (String) getGroup(groupPosition);

        if(convertView == null) {
            LayoutInflater layoutInflater = LayoutInflater.from(this.context);
            convertView =
                    layoutInflater.inflate(R.layout.ingredient_list_group, null);
        }

        //handle textView
        final TextView listHeader =
                (TextView) convertView.findViewById(R.id.ingredientGroupTextView);
        listHeader.setTypeface(null, Typeface.BOLD);
        listHeader.setText(headerTitle);

        //handle button
        Button deleteButton = (Button) convertView.findViewById(R.id.deleteButton);
        deleteButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //remove item from list
                Log.d("TESTING", "item should be removed");
                this.listDataHeader.remove(groupPosition);
                this.listDataChild.remove(groupPosition);
                notifyDataSetChanged();
            }
        });

        return convertView;
    }

    @Override
    public boolean hasStableIds() {
        return false;
    }

    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return true;
    }
}
Glissando answered 16/7, 2015 at 11:48 Comment(0)
B
0

Here is completely workable solution. The main idea - don't use OnClickListener inside Adapter, you have to use ExpandableListView.setOnChildClickListener() instead.

MyFragment.java

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ExpandableListView;

public class MyFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
        String[][] data = {
                {"Rice, cups", "Chiken, breasts", "Milk, gallons", "Beans, cups"},
                {"Orange, lemon", "Tomato, potato", "Onion, garlic"},
                {"Apple, pinapple", "Grapes, pear", "Cherry, berry", "Banana, strawberry", "Peach, apricot"},
        };

        final ChoiceAdapter adapter = new ChoiceAdapter(data);
        final ExpandableListView listView = new ExpandableListView(inflater.getContext());

        listView.setGroupIndicator(null);
        listView.setAdapter(adapter);
        listView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
            @Override
            public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
                adapter.selectChild(groupPosition, childPosition);
                listView.collapseGroup(groupPosition);
                // NOTE you even don't need to call notifyDataSetChanged() if you call collapseGroup()
                // adapter.notifyDataSetChanged();
                return true;
            }
        });

        return listView;
    }
}

ChoiceAdapter.java

import android.graphics.Typeface;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.TextView;

import java.util.Arrays;

public class ChoiceAdapter extends BaseExpandableListAdapter {
    private final String[][] mData;
    private final int[] mChoice;

    public ChoiceAdapter(String[][] data) {
        mData = data;
        mChoice = new int[data.length];
        Arrays.fill(mChoice, -1);
    }

    @Override
    public int getGroupCount() {
        return mData.length;
    }

    @Override
    public int getChildrenCount(int groupPosition) {
        return mData[groupPosition].length;
    }

    @Override
    public Integer getGroup(int groupPosition) {
        return mChoice[groupPosition];
    }

    @Override
    public String getChild(int groupPosition, int childPosition) {
        return mData[groupPosition][childPosition];
    }

    @Override
    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
        if (convertView == null) {
            // TODO inflate layout from xml instead
            convertView = new TextView(parent.getContext());
        }

        // TODO use ViewHolder pattern instead
        TextView textView = (TextView) convertView;
        textView.setTypeface(null, Typeface.BOLD);

        int childPosition = getGroup(groupPosition);
        if (childPosition < 0) {
            textView.setText("Choose an ingredient");
        } else {
            textView.setText(getChild(groupPosition, childPosition));
        }

        return convertView;
    }

    @Override
    public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
        if (convertView == null) {
            // TODO inflate layout from xml instead
            convertView = new TextView(parent.getContext());
        }

        // TODO use ViewHolder pattern instead
        TextView textView = (TextView) convertView;
        textView.setText(getChild(groupPosition, childPosition));

        return convertView;
    }

    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return true;
    }

    public void selectChild(int groupPosition, int childPosition) {
        mChoice[groupPosition] = childPosition;
    }

    @Override
    public long getGroupId(int groupPosition) { return 0; }

    @Override
    public long getChildId(int groupPosition, int childPosition) { return 0; }

    @Override
    public boolean hasStableIds() { return false; }
}
Bairam answered 16/7, 2015 at 12:50 Comment(1)
This is not the reason why his app is crashing and the adapter not working.Nullify

© 2022 - 2024 — McMap. All rights reserved.