3. Changes

James Shvarts edited this page Dec 11, 2018 · 1 revision

A Change is a result of transforming an Action. When loading notes, for instance, the following changes can occur:

  1. loading change
  2. success (notes data) or error change

Each Action can produce one or more Changes. Normally (but not always), a Change is a result of interacting with the Domain layer.

Changes are implemented as Kotlin sealed classes. In the above example, we can represent the Changes as follows:

    sealed class Change {
        object Loading : Change()
        data class Notes(val notes: List<Note>) : Change()
        data class Error(val throwable: Throwable?) : Change()
    }

The ViewModel will transform this Action into Changes as follows:

    val loadNotesChange = actions.ofType<Action.LoadNotes>()
        .switchMap {
            loadNoteListUseCase.loadAll()
                .subscribeOn(Schedulers.io())
                .toObservable()
                .map<Change> { Change.Notes(it) }
                .defaultIfEmpty(Change.Notes(emptyList()))
                .onErrorReturn { Change.Error(it) }
                .startWith(Change.Loading)
        }

Note how powerful (yet concise and expressive) the RxJava chain above is. We load notes from the Doman layer. Before we begin, we emit a Loading Change. If there are no notes found, we emit an empty list. If we encounter an error, we emit an Error Change. All of that in just a few lines of composable functional code!

Most of the time, your ViewModels will contain multiple RxJava chains generating Changes. For instance, the sample app defines several changes in the NoteDetailViewModel:

    val loadNoteChange = actions.ofType<Action.LoadNoteDetail>()
        .switchMap { action ->
            noteDetailUseCase.findById(action.noteId)
                .subscribeOn(Schedulers.io())
                .toObservable()
                .map<Change> { Change.NoteDetail(it) }
                .onErrorReturn { Change.NoteLoadError(it) }
                .startWith(Change.Loading)
        }

    val deleteNoteChange = actions.ofType<Action.DeleteNote>()
        .switchMap { action ->
            noteDetailUseCase.findById(action.noteId)
                .subscribeOn(Schedulers.io())
                .flatMapCompletable { deleteNoteUseCase.delete(it) }
                .toSingleDefault<Change>(Change.NoteDeleted)
                .onErrorReturn { Change.NoteDeleteError(it) }
                .toObservable()
                .startWith(Change.Loading)
        }

Later we merge them into a single stream to be sent to the Reducer:

    val allChanges = Observable.merge(loadNoteChange, deleteNoteChange)
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.