• After 15+ years, we've made a big change: Android Forums is now Early Bird Club. Learn more here.

ExpandableListAdapter | Remove Group and Add Child - both on button click

Adam123

Lurker
I'm building a small application using Firebase Real Time Database.

The app lists shifts per given date.

Each shift is displayed as a group item and every employee that is registered to this shift is displayed as a child in that group.

I used an ArrayList of ParentHeader class for the group-items and an ArrayList of Strings for the children-items (to list the employee's name) - This may change in the future for a more complex object.

This is my code:
Code:
public class DateActivity extends ExpandableListActivity {
   // date, isAdmin and name are passed to this activity using shared preferences
       private String date, name;
       private boolean isAdmin;
       // Adapter declaration
       private CustomExpandableListAdapter mAdapter;
       // ArrayList to store group items
       private ArrayList<ParentHeader> parents = new ArrayList<>();

       @Override
       protected void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           setContentView(R.layout.activity_date);
    // initialize date, isAdmin, name here

           // Fetch all shifts for selected date from Firebase
           Validator.fetchShiftsPerDayFromDB(date, new Callback()
           {
               @Override
               void shiftsCallback(ArrayList<Shift> shifts)
               {
                   super.shiftsCallback(shifts);
                   if (shifts == null)
                       return;

                   for(Shift shift : shifts){
                       parents.add(new ParentHeader(shift));
                   }

                   mAdapter = new CustomExpandableListAdapter();
                   setListAdapter(mAdapter);
               }
           });
}

   private class CustomExpandableListAdapter extends BaseExpandableListAdapter
   {
       private LayoutInflater inflater;

       public CustomExpandableListAdapter()
       {
           // Create Layout Inflator
           inflater = LayoutInflater.from(DateActivity.this);
       }

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

       @Override
       public int getChildrenCount(int groupPosition)
       {
           int size = 0;
           if(parents.get(groupPosition).getEmployeesList() != null)
               size = parents.get(groupPosition).getEmployeesList().size();
           return size;
       }

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

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

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

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

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

       @Override
       public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parentView)
       {
               final ParentHeader parentHeader = parents.get(groupPosition);

               // Inflate group_row.xml file for group rows
               convertView = inflater.inflate(R.layout.group_row, parentView, false);
// Get group_row.xml file elements and set values here

           //Add User to selected shift
           convertView.findViewById(R.id.add).setOnClickListener(v -> {
               //TODO insert constraints
              Validator.addUserToShiftInDB(parentHeader.getShift().getKey(), date, name, new Callback() {
                  @Override
                  void onUserAssignedToShiftCallback() {
                      super.onUserAssignedToShiftCallback();
                       // TODO attach as child-item to chosen group-item
                  }
              });

           });

               convertView.findViewById(R.id.delete).setOnClickListener(v -> {
                   if(isAdmin){
                       //TODO open confirmation window
                       Validator.deleteShiftFromDateInDB(parentHeader.getShift().getKey(), date, new Callback() {
                           @Override
                           void onDeletedShiftCallback() {
                               super.onDeletedShiftCallback();
// shift gets deleted in Firebase but the expandable list does not refresh
                               parents.remove(groupPosition);
                               mAdapter.notifyDataSetChanged();
                           }
                       });

                   }
                   else{
                       Toast.makeText(getApplicationContext(),"Admin privileges required", Toast.LENGTH_SHORT).show();
                   }
               });
               return convertView;
       }

         @Override
       public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parentView)
       {
           final ParentHeader parentHeader = parents.get(groupPosition);
           final String childString = parentHeader.getEmployeesList().get(childPosition);

           // Inflate child_row.xml file for child rows
           convertView = inflater.inflate(R.layout.child_row, parentView, false);

           // Get child_row.xml file elements and set values here

           return convertView;
       }

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

       @Override
       public boolean areAllItemsEnabled()
       {
           return true;
       }

       @Override
       public boolean isEmpty()
       {
           return ((parents == null) || parents.isEmpty());
       }

       @Override
       public void notifyDataSetChanged()
       {
           // Refresh List rows
           super.notifyDataSetChanged();
       }
   }
}

The ParentHeader class which stores data on each group-item.
Code:
public class ParentHeader {
   private Shift shift;
   private boolean isDeleteButtonClicked, isAddButtonClicked, isHeaderClicked;
   // Array to store child objects
   private ArrayList<String> employeesList = new ArrayList<>();

   public ParentHeader() { }

   public ParentHeader(Shift shift) {
       this.shift = shift;
   }

// Getters/Setters here
}

and finally the Shift class which stores data for every shift.
Code:
public class Shift {
   private String startTime, endTime, name, key;
   private Integer wage, numOfEmps, employees;


   public Shift(){}

   public Shift(String name, String startTime, String endTime, Integer wage, Integer numOfEmps, Integer employees) {
       this.startTime = startTime;
       this.endTime = endTime;
       this.name = name;
       this.wage = wage;
       this.numOfEmps = numOfEmps;
       this.employees = employees;
// key field is assigned a value when fetching each shift from firebase using .getKey() method

   }
//Getters/Setters here
}

First Problem

Removing a group item from an ExpandableListAdapter on button click on the parent-item.

As you can see above I'm calling parents.remove and then notifyDataSetChanged in my getGroupView method, but the list does not get refreshed.

It does however work if I reload the activity or by moving to another activity and then back to this one.

I wish for it to work dynamically.

Second Problem

Adding a child to a group on button click of the parent-item.

My database is structured as follows:
Code:
"20190528" : {
    "-LfvCgavw5bYUJzULE6E" : {
      "employees" : 0,
      "endTime" : "00:00",
      "name" : "test1",
      "numOfEmps" : 2,
      "startTime" : "00:00",
      "wage" : 2
    }
  }

When a new shift has been added to a date by the Admin.

Now I wish for every User to be able to assign themselves to one of the shifts and for their name to be displayed as a child-item for that group-item shift.

After they click their chosen group-item shift, their data should be stored in Firebase like so:
Code:
"20190528" : {
    "-LfvCgavw5bYUJzULE6E" : {
      "employees" : {
        "generatedKey1" : "John Smith",
        "generatedKey2" : "Jane Doe"
      },
      "endTime" : "00:00",
      "name" : "test1",
      "numOfEmps" : 2,
      "startTime" : "00:00",
      "wage" : 2
    }
  }

I wish for every name stored under employees to be displayed as a child-item under its corresponding group-item shift. Dynamically of course.

I'd appreciate help in solving these two problems. I'm new to Android developement and do not have much experience with it.

Thank you.
 
This might help with your first problem: it appears you need to run notifyDataSetChanged() on the main (UI) thread:
https://stackoverflow.com/a/3669526

I'm not clear on what you're trying to accomplish with the second question. Are you having problems updating things in firebase, or is it also a display update problem?
 
This might help with your first problem: it appears you need to run notifyDataSetChanged() on the main (UI) thread:
https://stackoverflow.com/a/3669526

I'm not clear on what you're trying to accomplish with the second question. Are you having problems updating things in firebase, or is it also a display update problem?
First of all, thank you for your answer.

I tried using runOnUiThread(() -> mAdapter.notifyDataSetChanged()) instead of just mAdapter.notifyDataSetChanged() but my Expandable List still doesn't get refreshed, as if it retains its previous state. By that I mean that if I have let's say 2 Headers currently in my List and I delete one of them, the List keeps the previous two Headers and "adds" the Header that wasn't deleted to its view.
Perhaps it has something to do with the cache?

As for the second problem, I only wish to know how to implement the insertion of a Child object to its corresponding Header. (i.e. When I click the button on the first Header on the List , a new object needs be inserted as a Child of the first Header. When I click the second Header's button, a new Child needs be inserted to that Header etc).
 
Sorry, I don't know why it wouldn't be updating then. I'm pretty new to this too - maybe someone with more experience can chime in on this.

For the second one, it seems that just adding an item to the relevant ArrayList would do it - you're talking about in the data, right? Or do you mean adding it into the database?
 
Back
Top Bottom