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
List after click to the button List All Budgets
All Groups Expandet
Before delete the last child of the last group
Remaining group show children twice
Last group this time with two children
Before deleting the last children of the last group
Correct result after deleting last child of last group
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
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
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
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
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