Publishing Actions
Our RosterViewModel
has a process()
method that our fragments call to publish actions. In the case of RosterListFragment
and RosterListAdapter
, this happens in a few places.
First, the user might modify a to-do item directly from the RecyclerView
by clicking the item’s CheckBox
. This sample app uses the data binding framework, and so the CheckBox
has a binding expression for the onCheckedChanged
event, routing it to the view (by way of RosterListAdapter
and its associated RosterRowHolder
for each row). That, in turn, eventually triggers a process()
call to publish an edit event:
public void edit(ToDoModel model, boolean isChecked) {
process(Action.edit(model.toBuilder().isCompleted(isChecked).build()));
}
Here, we:
- Get a
Builder
with the current item’s data fromtoBuilder()
- Call
isCompleted()
to mark the item as completed -
build()
a newToDoModel
from thatBuilder
- Pass that
ToDoModel
to theedit()
helper method onAction
to create the properAction
instance - Hand that
Action
over toprocess()
Similarly, if the user clicks the “delete” icon in multi-select mode, we want to get confirmation from the user, then delete those items. That eventually gets handled by a requestDelete()
method on RosterListFragment
, which shows a Snackbar
and calls process()
for delete()
Action
if the user clicks the Snackbar
action:
public void requestDelete(int selectionCount) {
Resources res=getResources();
String msg=res.getQuantityString(R.plurals.snackbar_delete,
selectionCount, selectionCount);
snackbar=Snackbar.make(getView(), msg, Snackbar.LENGTH_LONG);
snackbar
.setAction(android.R.string.ok, view -> {
process(Action.delete(adapter.getSelectedModels()));
adapter.exitMultiSelectMode();
})
.show();
}
Our filter mode is part of our view state, and the filter mode is something that we want to persist. That is why we have actions related to filter mode, and why when the user toggles those menu items we call process()
to publish the associated actions:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.add:
((Contract)getActivity()).addModel();
return(true);
case R.id.all:
filterAll.setChecked(true);
process(Action.filter(FilterMode.ALL));
return(true);
case R.id.completed:
filterCompleted.setChecked(true);
process(Action.filter(FilterMode.COMPLETED));
return(true);
case R.id.outstanding:
filterOutstanding.setChecked(true);
process(Action.filter(FilterMode.OUTSTANDING));
return(true);
case R.id.export:
export();
return(true);
case R.id.share:
share();
return(true);
case R.id.backup:
backup();
return(true);
case R.id.restore:
restore();
return(true);
}
return(super.onOptionsItemSelected(item));
}
Note that in none of these cases do we update the fragment’s UI based on these events. Of course, in the case of the CheckBox
, that happens automatically. But if the user deletes items or changes the filter mode, we do not update the RecyclerView
. Instead, we just publish the actions, and we only update the UI in render()
, when we get the updated view state.
One oddball action is the “load” action. Somewhere along the line, when we start up the app, we need to load our data — without the data, we have no view state and we have nothing to render. But, at the same time, we only want to load the data when we are starting out, not after a configuration change, so we cannot readily use lifecycle methods on the activity or fragments to trigger a load. Instead, RosterViewModel
, as part of its constructor, publishes a load()
Action
on its own. Hence, when we create the view-model, we initiate loading of the data. Since a ViewModel
is only created once per “logical” activity instance (taking into account configuration changes), we only load this data once for the entire activity. This might be insufficient in a more elaborate app, where multiple activities might share a common repository, but it will suffice here.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.