Search code examples
androidexpandablelistviewexpandablelistadapter

ExpandableListAdapter delete group problem


I have a big problem deleting a group from an ExpandableListView. Even after google a lot and trying a lot of tutorials and examples I was not able to solve it.

Though I have a lot of programming experiences I am relative knew to Android programming. So I am sure there are many things in the source which are not yet well done. But as of now I wanted to focus on the problem with a wrong view after deleting a group from the list.

To give a good overview to the problem here are some screenshots

Start of the App

The MainActivity

List after click to the button List All Budgets

The not expanded groups on BudgetListActivity

All Groups Expandet

Groups expandet

Before delete the last child of the last group

before deleting last child of group 2

Remaining group show children twice

remaining group show children twice

Last group this time with two children

last group has two children

Before deleting the last children of the last group

before delteing last child of group 2

Correct result after deleting last child of last group

result after deleting last child of group 2

I hope the problem becomes clear. If the last group has only one child and this was deleted the whole group will be deleted by the app - but than the children of the first group show up twice.

During a debugging session I checked all the ressources behind the data and they are all ok. If I go back to MainActivity and start the list again the view ist totally correct. So it must be a problem of an incorrect population after deleting a whole group.

As you can see if I only delete the last child from a last group with two childs the populating of the whole list is correct.

Here are some more information about the app:

  • I use a room database with two tables holding the data.

    One tabel contains the categories with name and id and the other tabel is for single budget records with the category id as an foreign key project database

In onCreate of the BudgetListActivity I created two DAO's budgetDAO and categoryDAO to get the data and fill the lists allBudgetsList and all CatList. With this informations I create a new array List allGroups with the structure I need for the view - Categories as header - budgets as children due to the foreign key

(just one remark here: meanwhile I tried already using a hashmap for the data given to the ExpandableListAdapter - but the result was the same wrong view population!)

There is a contentView "budget_expandable_list" which is set to the ExpandableListAdapter. The adapter should populate the groups and childs for this list using the data from the ArrayList "allGroups"

This is the structure of the app app structure

It could be that there are some ressources which are not used actually.

I will give now the soure code for the importand classes

BudgetListActivity:

package com.wbapps.WBEasyBudgetManagement;

import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.CoordinatorLayout;
import android.support.v7.app.AppCompatActivity;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ExpandableListView;
import android.widget.Toast;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class BudgetListActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
    CoordinatorLayout coordinatorLayout;
    private SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");

    //wb, 23Oct2018: now using an array list for the expandable list adapter
    ArrayList<Group> allGroups = new ArrayList();

    private ArrayAdapter adapter;
    private final int REQUEST_CODE_EDIT = 1;

    private BudgetDAO budgetDAO;
    private CategoryDAO categoryDAO;

    List<Budget> allBudgetsList;
    List<Category> allCatsList;

    ExpandableListView expListView;
    List<String> expListViewTitle;
    ExpandableListAdapter expAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.budget_expandable_list);

        if (allGroups.size() > 0 ) {allGroups.clear();}
        //get instances for DAO's of db from MainActivity
        budgetDAO = MainActivity.getBudgetDAO();
        categoryDAO = MainActivity.getCategoryDAO();

        //the list for budgets and categories
        allBudgetsList = budgetDAO.getBudgets();
        allCatsList = categoryDAO.getCategories();

        //temporary Group-Object for the ArrayList allGroups
        Group tmpGroup;
        double sumExpenses = 0;
        //Start with reading all categories
        for (int i=0;i<allCatsList.size(); i++) {
            String tmpCat = allCatsList.get(i).getCategory();
            tmpGroup = new Group(tmpCat);
            sumExpenses = 0.0;
            //now read all budgets for the current category and fill the rest of the temporary Group-Object
            for (int j=0;j<allBudgetsList.size();j++){
                if (allBudgetsList.get(j).getCategoryId() == allCatsList.get(i).getId()){
                    //tmpGroup.budgetId = allBudgetsList.get(j).getId();
                    tmpGroup.catId = allBudgetsList.get(j).getCategoryId();

                    tmpGroup.children.add(Arrays.asList
                            (
                                    " Date: " + sdf.format(allBudgetsList.get(j).getDateTime())
                                            + " - Expenses: " + Double.toString(allBudgetsList.get(j).getExpenses()),
                                    Long.toString(allBudgetsList.get(j).getId())
                            )
                    );

                    sumExpenses = sumExpenses + allBudgetsList.get(j).getExpenses();
                    tmpGroup.sumExpenses = sumExpenses;
                }
            }
            //if at least one children for the current category was found
            // =>> write all the group information the the array list
            if (tmpGroup.children.size() > 0 ) {allGroups.add(tmpGroup);}
        }
        expListView = (ExpandableListView) findViewById(R.id.expandableList);
        expAdapter = new ExpandableListAdapter(this, allGroups);
        expListView.setAdapter(expAdapter);
        expListView.setOnItemClickListener(this);
        registerForContextMenu(expListView);
    }

    @Override
    public void onCreateContextMenu(ContextMenu contMenu, View v,
                                    ContextMenu.ContextMenuInfo contextMenuInfo) {

        super.onCreateContextMenu(contMenu, v, contextMenuInfo);
        ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) contextMenuInfo;

        int type = ExpandableListView.getPackedPositionType(info.packedPosition);
        int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
        int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition);

        // Show context menu for groups
        if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
            contMenu.setHeaderTitle("Budget");
            contMenu.add(R.string.context_editBudget);
            contMenu.add(R.string.context_delBudget);

            // Show context menu for children
        } else if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
            contMenu.setHeaderTitle("Child");
            contMenu.add(R.string.context_editChild);
            contMenu.add(R.string.context_delChild);
        }
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        Integer tmpInt = item.getItemId();
        ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) item
                .getMenuInfo();

        int type = ExpandableListView.getPackedPositionType(info.packedPosition);
        int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
        int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition);
        //TextView vItem = info.targetView.findViewById(R.id.context_editBudget);

        if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
            //Toast.makeText(this, "Click auf Group: " + Integer.toString(item.getGroupId()), Toast.LENGTH_SHORT).show();

            if (item.getTitle().toString().equals(getString(R.string.context_editBudget))){
                Toast.makeText(this, "Edit Budget clicked in Budget Context Menu", Toast.LENGTH_SHORT).show();
            }

            if (item.getTitle().toString().equals(getString(R.string.context_delBudget))){
                int size = allGroups.get(groupPosition).children.size();
                for (int i = 0; i<size; i++) {
                    budgetDAO.delAllBudgetsForCategory(allGroups.get(groupPosition).catId);
                }
                allGroups.remove(groupPosition);
                //expAdapter.notifyDataSetChanged();
                if (allGroups.size() == 0){
                    Intent intent = new Intent(BudgetListActivity.this, MainActivity.class);
                    startActivity(intent);
                }
            }
        }

        if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {

            if (item.getTitle().toString().equals(getString(R.string.context_editChild))){
                Toast.makeText(this, "Edit Child clicked in Child Context Menu", Toast.LENGTH_SHORT).show();
            }

            if (item.getTitle().toString().equals(getString(R.string.context_delChild))){
                //wb, 27Oct2018: Delete the selected child for a budget with given category
                budgetDAO.delBudgetChildForCategory(Integer.parseInt(allGroups.get(groupPosition).children.get(childPosition).get(1)));
                allGroups.get(groupPosition).children.remove(childPosition);

                //expAdapter.notifyDataSetChanged();

                //wb, 28Oct2018: If no more budget rows available delete the whole budget for category
                if (allGroups.get(groupPosition).children.size() == 0) {
                    allGroups.remove(groupPosition);
                    //expAdapter.notifyDataSetChanged();
                    //expAdapter.notifyDataSetChanged();
                    if (allGroups.size() ==0){
                        Intent intent = new Intent(BudgetListActivity.this, MainActivity.class);
                        startActivity(intent);
                    }

                }
                /*
                else {
                    //allGroups.get(groupPosition).sumExpenses = 0.0;
                    //allGroups.get(groupPosition) = expAdapter.getSum(groupPosition)
                    for (int i = 0; i < allBudgetsList.size(); i++) {
                        if (allBudgetsList.get(i).getCategoryId() == allGroups.get(groupPosition).catId) {
                            allGroups.get(groupPosition).sumExpenses =
                                    allGroups.get(groupPosition).sumExpenses + allBudgetsList.get(i).getExpenses();
                        }
                    }
                }*/
            }
        }
        expAdapter.notifyDataSetChanged();
        //return super.onContextItemSelected(item);
        return true;
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Budget budget = (Budget)adapter.getItem(position);
        editEntry(budget, position);
    }

    private void editEntry(Budget budget, int position) {
        Intent intent = new Intent(this, EditBudgetActivity.class);
        intent.putExtra("position", position);
        startActivityForResult(intent, REQUEST_CODE_EDIT);
    }
}

As you can see I use a context menu for editing and deleting groups and/or childs. Some features are not yet fully implemented. Please understand that I will first focus on my main problem with the correct population of the ExpandableView.

Also other things - like the incorrect update of the summery of the expences after deleting a child - are not yet very important and will be done later.

Here the class for a Group Object:

package com.wbapps.WBEasyBudgetManagement;

import java.util.ArrayList;
import java.util.List;

public class Group {
    public long budgetId;
    public long catId;

    public String category;
    public final List<List<String>> children = new ArrayList<List<String>>();
    public final List<Long> BudIds = new ArrayList<Long>();

    public double sumExpenses;

    public Group(String pcategory) {
        category = pcategory;
    }

}

Here is the ExpandableListAdapter source:

package com.wbapps.WBEasyBudgetManagement;

import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.CheckedTextView;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Currency;
import java.util.Locale;

public class ExpandableListAdapter extends BaseExpandableListAdapter{
    Context context;
    Locale locale;
    Currency curr;
    //array list to take the data for the list from the activity
    private final ArrayList<Group> allGroups;
    public LayoutInflater inflater;
    public AppCompatActivity activity;
    public int times = 0;

    //Constructor for ExpandableListAdapter
    //public ExpandableListAdapter(AppCompatActivity act, SparseArray<Group> groups) {
    public ExpandableListAdapter(AppCompatActivity act, ArrayList<Group> allGroups) {
        this.activity = act;
        this.allGroups = allGroups;
        inflater = act.getLayoutInflater();
    }

    @Override
    public View getGroupView(int groupPosition, boolean isExpanded,
                             View convertView, ViewGroup parent) {
        times = times + 1;
        Log.d("Info getGroupView","In getGroupView " + Integer.toString(times) + " times");
        for (Locale wbLocale : Locale.getAvailableLocales()) {
            //Log.d("LOCALES", wbLocale.getLanguage() + "_" + wbLocale.getCountry() + " [" + wbLocale.getDisplayName() + "]");
            if (wbLocale.getCountry().equals("PH")) {
                curr = Currency.getInstance(wbLocale);
                curr.getSymbol(wbLocale);
                break;
            }
        }

        if (convertView == null || convertView.findViewById(R.id.tvCatGroup)==null){
            convertView = inflater.inflate(R.layout.list_row_group, null);
        }

        convertView = inflater.inflate(R.layout.list_row_group, null);
        String tmpCat = allGroups.get(groupPosition).category;
        Group tmpGroup = new Group(tmpCat);
        sortList();

        Group group = (Group) getGroup(groupPosition);
        //((CheckedTextView) convertView).setText(group.category + "\nTotal Expenses: " + group.sumExpenses + " " + curr.getSymbol());
        ((CheckedTextView) convertView).setText(group.category + "\nTotal Expenses: " + getSum(groupPosition) + " " + curr.getSymbol());
        ((CheckedTextView) convertView).setChecked(isExpanded);
        return convertView;
    }

    /* wb, 18Sep2017: sort the list_selectedShoppingItems list */
    public void sortList() {
        Collections.sort(allGroups, new Comparator<Group>() {
            @Override
            public int compare(Group content1, Group content2) {
                /* ignore case sensitivity */
                return content1.category.compareToIgnoreCase(content2.category);
            }
        });
    }

    @Override
    public View getChildView(int groupPosition, final int childPosition,
                             boolean isLastChild, View convertView, ViewGroup parent)
    {
        if(childPosition < getChildrenCount(groupPosition)-1) {
            //holds the detail string for one child
            final String children = (String) getChild(groupPosition, childPosition);
            if (convertView == null || convertView.findViewById(R.id.tvChildRow)==null)
                convertView = inflater.inflate(R.layout.list_row_details, null);

            convertView = inflater.inflate(R.layout.list_row_details, null);
            TextView txtChildRow = (TextView)convertView.findViewById(R.id.tvChildRow);
            txtChildRow.setText(children + " " + curr.getSymbol());
            convertView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(activity, children + " " + curr.getSymbol(),
                            Toast.LENGTH_SHORT).show();
                }
            });
        }

        //children is the last one
        if(childPosition == getChildrenCount(groupPosition)-1)
        {
            if (convertView == null || convertView.findViewById(R.id.tvSum)==null)
            convertView = inflater.inflate(R.layout.listview_footer,null);
            TextView txtFooter = (TextView)convertView.findViewById(R.id.tvSum);
            //txtFooter.setText("Total expenses: " + allGroups.get(groupPosition).sumExpenses + " " + curr.getSymbol() );
            txtFooter.setText("Total expenses: " + getSum(groupPosition) + " " + curr.getSymbol() );
            //Log.e(TAG, "getChildView - sumExpenses: "+txtFooter.getText().toString());
        }
        convertView.setLongClickable( true);
        return convertView;
    }

    @Override
    public Object getChild(int groupPosition, int childPosition) {

        return allGroups.get(groupPosition).children.get(childPosition).get(0);
    }

    public Object getSum(int groupPosition) {
        return allGroups.get(groupPosition).sumExpenses;
    }

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

    //Add 1 to childCount. The last row is used as footer to childView
    @Override
    public int getChildrenCount(int groupPosition) {
        return allGroups.get(groupPosition).children.size() +1;
    }

    @Override
    public Object getGroup(int groupPosition) {
        return allGroups.get(groupPosition);
    }

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

    @Override
    public void onGroupCollapsed(int groupPosition) {
        super.onGroupCollapsed(groupPosition);
    }

    @Override
    public void onGroupExpanded(int groupPosition) {
        super.onGroupExpanded(groupPosition);
    }

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

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

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

    @Override
    public void notifyDataSetChanged() {
        super.notifyDataSetChanged();
    }
}

Some remarks might be helpful: - in getChildrenCount I added 1 to the number of size because I use one last children as a footer to show the summary of expenses

  • for a better understanding here is a picture of the list "allGroups" allGroups List

I hope I could support you with all the neccessary informations. Please let me know if some is missing. I will add it soon.

Hopefully there is someone out there with a solution for me. Have a nice day Andreas


Solution

  • meanwhile I found the reason for that behaviour. There is a method "getGroupID" at the end of the source code of the adapter. The return value here was set to 0 which caused the trouble. It has to be set to the groupPosition and then it works!

    @Override
    public long getGroupId(int groupPosition) {
        /* wb, 10Nov2018: this statement was due to the error of deleting a last child of a group
        With "return 0" the children of the remaining group was shown twice !!!
        return 0;
        */
        return groupPosition;
    }
    

    This is hopefully helpful to all who also run into this problem. Have a nice time Andreas