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:

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.