The Open Event Organizer Android App was supporting deletion for only one item, and didn’t support the edit option. It needed to support multiple deletions for items and the edit operation. This PR and hence the blog covers details on how to handle multiple deletions and implementing the edit option.
Here are the steps I followed to implement these options from scratch:
Step 1: Create toolbar menu
First create a menu for toolbar with the desired toolbar actions (delete and edit in our case)
<?xml version=“1.0” encoding=“utf-8”?> <menu xmlns:android=“http://schemas.android.com/apk/res/android” xmlns:app=“http://schemas.android.com/apk/res-auto”> <item android:id=“@+id/del” android:title=“Delete” android:icon=“@drawable/ic_delete” android:visible=“false” app:showAsAction=“ifRoom”> </item> <item android:id=“@+id/edit” android:title=“Update” android:icon=“@drawable/ic_edit” android:visible=“false” app:showAsAction=“ifRoom”> </item> </menu> |
Step 2: Setup helper options:
We need to keep track of which items have been selected and also be able to get the count of these selected items many times. Therefore, we need to setup the following helper methods isTrackSelected() and countSelected()
Both these methods make use of the HashMap:
private final Map<Long, ObservableBoolean> selectedTracks = new HashMap<>(); |
Every time a method uses isTrackSelected() , it checks whether or not there is an entry of that track in the HashMap selectedTracks. If not, it adds the entry and returns the corresponding default set ObservableBoolean.
public ObservableBoolean isTrackSelected(Long trackId) { if (!selectedTracks.containsKey(trackId)) selectedTracks.put(trackId, new ObservableBoolean(false)); return selectedTracks.get(trackId); } private int countSelected() { int count = 0; for (Long id : selectedTracks.keySet()) { if (selectedTracks.get(id).get()) Count++; } return count; } |
Step 3: Handling clicks
The logic to be followed had to be consistent with existing popular apps, which users are habitual of using. It thus had to be intuitive. And so here’s the logic we follow:
Click -> Opens intent (if toolbar not already active)
Click -> Selects item (if toolbar is active and item is not already selected)
Click -> Deselects item (if item already selected)
Click -> Deselects item and de-activates toolbar (if the already selected item was the only one)
Long Click -> Activates toolbar and selects item (if toolbar not already active)
Long Click -> Selects item (if toolbar already active)
> Handling single clicks:
Following the described logic, we see if a toolbar is not active, we simply open the detailed view of the already selected item.
public void click(Long clickedTrackId) { if (isToolbarActive) { if (countSelected() == 1 && isTrackSelected(clickedTrackId).get()) { selectedTracks.get(clickedTrackId).set(false); resetToolbarDefaultState(); } else if (countSelected() == 2 && isTrackSelected(clickedTrackId).get()) { selectedTracks.get(clickedTrackId).set(false); getView().changeToolbarMode(true, true); } else if (isTrackSelected(clickedTrackId).get()) selectedTracks.get(clickedTrackId).set(false); else selectedTracks.get(clickedTrackId).set(true); if (countSelected() > EDITABLE_AT_ONCE) getView().changeToolbarMode(false, true); } else getView().openSessionsFragment(clickedTrackId); } |
> Handling long clicks:
If the toolbar is active, long clicks behave same as simple clicks. Otherwise, we first add them to the selectedTracks, set isToolbarActive to true and change toolbar mode to activate edit and delete options.
public void longClick(Track clickedTrack) { if (isToolbarActive) click(clickedTrack.getId()); else { selectedTracks.get(clickedTrack.getId()).set(true); isToolbarActive = true; getView().changeToolbarMode(true, true); } } public void resetToolbarDefaultState() { isToolbarActive = false; getView().changeToolbarMode(false, false); } |
Step 4: Add relevant methods in presenter
We need to add the deleteSelectedTracks() method to the presenter so that we can delete all the tracks selected for deletion.
This methods only iterates from iterable selectedTracks.entrySet() and then executes the already existing deleteTrack() method from the presenter
private void deleteTrack(Long trackId) { trackRepository .deleteTrack(trackId) .compose(disposeCompletable(getDisposable())) .compose(progressiveErroneousCompletable(getView())) .subscribe(() -> { getView().showTrackDeleted(“Track Deleted”); loadTracks(true); selectedTracks.remove(trackId); Logger.logSuccess(trackId); }, Logger::logError); } public void deleteSelectedTracks() { Observable.fromIterable(selectedTracks.entrySet()) .compose(dispose(getDisposable())) .compose(progressiveErroneous(getView())) .doFinally(() -> { getView().showMessage(“Tracks Deleted”); resetToolbarDefaultState(); }) .subscribe(entry -> { if (entry.getValue().get()) { deleteTrack(entry.getKey()); } }, Logger::logError); } |
This is how the result looks like:
Resources
- Open Event Organizer Android App: Pull Request #964
https://github.com/fossasia/open-event-orga-app/pull/964 - Google developer training: contextual action-bars:
https://google-developer-training.gitbooks.io/android-developer-fundamentals-course-concepts/content/en/Unit%202/42_c_menus.html