androidexpandablelistview

Editing children in an ExpandableListView


I am trying to use an ExpandableListView to display a list of items with their children. I would like each child to have 2 radio buttons (Yes and No). When the No button is clicked, an EditText should appear in which the user should enter some text.

When the user clicks the save button I would like to save the data.

After some research I think it is best to update the child objects as they change rather than try and retrieve it all at once on a button click. So my code attempts to show this in action. If this is not the best approach please let me know.

My issue is that UNEXPECTED children are updated when I edit one.

I've looked at the following links (amongst many!) but not had success.

https://www.digitalocean.com/community/tutorials/android-expandablelistview-example-tutorial how to fetch data from multiple edit text in child view of expandable list view? https://www.geeksforgeeks.org/baseexpandablelistadapter-in-android-with-example/

ExpandableListAdapter

public class MyExpandableListAdapter extends BaseExpandableListAdapter{
    private Context context;
    private List<Parent> parents;
    private HashMap<Parent, List<Child>> children;

    public MyExpandableListAdapter(Context context, List<Parent> parents, HashMap<Parent, List<Child>> children){
        this.context = context;
        this.parents = parents;
        this.children = children;
    }

    @Override
    public Child getChild(int listPosition, int expandedListPosition){
        return this.children.get(this.parents.get(listPosition)).get(expandedListPosition);
    }

    @Override
    public long getChildId(int listPosition, int expandedListPosition){
        return this.getChild(listPosition, expandedListPosition).getId();
    }

    @Override
    public View getChildView(final int listPosition, final int expandedListPosition,
                             boolean isLastChild, View convertView, ViewGroup parent) {

        if (convertView == null)
        {
            LayoutInflater inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.child_layout, parent, false);
        }

         TextView text = (TextView) convertView.findViewById(R.id.child_text);
         RadioButton yesButton = (RadioButton)convertView.findViewById(R.id.yes_button);
         RadioButton noButton = (RadioButton)convertView.findViewById(R.id.no_button);
         EditText negativeResponse = (EditText)convertView.findViewById(R.id.no_response);

        Child child = getChild(listPosition, expandedListPosition);
        child.setResponse(true);

        if (child != null)
        {
            text.setText(child.getText());
        }

        negativeResponse.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (!hasFocus){
                    String response = negativeResponse.getText().toString();
                    child.setNegativeResponse(response);
                    child.setResponse(response == null || response.isEmpty());
                }
            }
        });

        yesButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                negativeResponse.setVisibility(View.GONE);
                child.setResponse(true);
                child.setNegativeResponse(null);
            }
        });

        noButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                negativeResponse.setVisibility(View.VISIBLE);
                child.setResponse(false);
            }
        });
        return convertView;
    }

    @Override
    public int getChildrenCount(int listPosition) {
        return this.children.get(this.parents.get(listPosition)).size();
    }

    @Override
    public Parent getGroup(int listPosition) {
        return this.parents.get(listPosition);
    }

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

    @Override
    public long getGroupId(int listPosition) {
        return this.getGroup(listPosition).getOid();
    }

    @Override
    public View getGroupView(int listPosition, boolean isExpanded,
                             View convertView, ViewGroup parent) {
        Parent parent = getGroup(listPosition);
        if (convertView == null) {
            LayoutInflater layoutInflater = (LayoutInflater) this.context.
                    getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = layoutInflater.inflate(R.layout.parent_layout, null);
        }
        TextView parentCode = (TextView) convertView.findViewById(R.id.code);
        parentCode.setText(parent.getCode());
        TextView parentDescription = (TextView) convertView.findViewById(R.id.description);
        parentDescription.setText(parent.getDescription());
        return convertView;
    }

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

    @Override
    public boolean isChildSelectable(int listPosition, int expandedListPosition) {
        return true;
    }
}

Parent

public class Parent {
    private int id;
    private String code;
    private String description;
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

Child

public class Child {
    private int id;
    private String text;
    private boolean response;
    private String negativeResponse;
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
    public String getText() {
        return questionText;
    }

    public void setText(String text) {
        this.text = text;
    }
    
    public boolean isAffirmativeResponse() {
        return response;
    }

    public void setResponse(boolean response) {
        this.response = response;
    }

    public String getNegativeResponse() {
        return negativeResponse;
    }

    public void setNegativeResponse(String negativeResponse) {
        this.negativeResponse = negativeResponse;
    }

Activity

public class MyActivity {
    private MyExpandableListAdapter listAdapter;
    private ExpandableListView dataView;
    @Override
    public void onResume() {
        dataView = (ExpandableListView) findViewById(R.id.dataView);
        this.listAdapter = new MyExpandableListAdapter(this.getContext(), this.getParents(), this.getChildren());
        dataView.setAdapter(this.listAdapter);
    }
    
    private List<Parent> getParents() {
        // Gets the list of parents
        
    }
    
    private List<Child> getChildren() {
        // Gets the list of children
    }
}

Parent-Layout

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/code"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/description"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:textStyle="bold" />

</RelativeLayout>

Child-Layout

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:paddingRight="5dp"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/child_text"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"/>

        <RadioGroup
            android:id="@+id/radio_group"
            android:orientation="horizontal"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/child_text">

            <RadioButton
                android:id="@+id/no_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="@string/no"/>

            <RadioButton
                android:id="@+id/yes_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:paddingRight="10dp"
                android:checked="true"
                android:text="@string/yes"/>
        </RadioGroup>
    </RelativeLayout>

    <EditText
        android:id="@+id/negative_response"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:inputType="textMultiLine"
        android:visibility="gone"/>
</RelativeLayout>

Say I have the data in the format

If I edit Child 1a, Child 2c mirrors the changes I made.

If I edit Child 1b, Child 2c mirrors the changes I made.

If I edit Child 1c, Child 2d mirrors the changes I made.

If I edit Child 1d, Child 2e mirrors the changes I made.

I feel I've made a really elementary mistake but I can't see it. How can I edit the correct child and ONLY the correct child so I can save the data?


Solution

  • It was down to the way I was picking which Child to display and how they were displaying. The fixed getChildView is below

        @Override
        public View getChildView(final int listPosition, final int expandedListPosition,
                                 boolean isLastChild, View convertView, ViewGroup parent) {
    
            if (convertView == null) {
                LayoutInflater inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                convertView = inflater.inflate(R.layout.child_layout, parent, false);
            }
    
            TextView text = (TextView) convertView.findViewById(R.id.child_text);
            final RadioButton yesButton = (RadioButton) convertView.findViewById(R.id.yes_button);
            final RadioButton noButton = (RadioButton) convertView.findViewById(R.id.no_button);
            final EditText negativeResponse = (EditText) convertView.findViewById(R.id.no_response);
    
            final Child child = getChild(listPosition, expandedListPosition);
            
            if (child != null) {
                text.setText(child.getText());
            }
    
            negativeResponse.setOnFocusChangeListener(new View.OnFocusChangeListener() {
                @Override
                public void onFocusChange(View v, boolean hasFocus) {
                    if (!hasFocus) {
                        response.setNegativeResponse(negativeResponse.getText().toString());
                    }
                }
            });
    
            yesButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    negativeResponse.setVisibility(View.GONE);
                    response.setPositiveQuestionResponse(true);
                    response.setNegativeResponse("");
    
                }
            });
    
            noButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    negativeResponse.setVisibility(View.VISIBLE);
                    response.setPositiveQuestionResponse(false);
    
                }
            });
            return convertView;
        }