Skip to content

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)
Clone this wiki locally