I'm wanting to utilise a architectural design that enables me to clearly designate input and outputs in my view model (How To Feed ViewModels) but am curious as to how I can best integrate the "working" part of the view model into this structure.
I have tended to use Actions (perhaps not very elegantly) to bind UI elements to the work they need to perform. The problem of course is that some of these Actions rely on view model properties so I can't create them in init() in the same was as Input and Outputs as the properties are not yet initialised. It's possible to work around this by defining them as private lazy vars and then exposing them via a struct that essentially presents a public interface to Action. It doesn't seem to flow very well though and I'm learning that if you're expending a lot of effort to get structure to bend to your will, it's probably a code smell. Code example below - suggestions welcome :-)
protocol PatientListViewModelType: ViewModelType { }
final class PatientListViewModel: PatientListViewModelType {
// MARK:- Protocol conformance
typealias Dependencies = HasPatientService
struct Input {
let patient: AnyObserver<Patient>
}
struct Output {
let sectionedPatients: Observable<[PatientSection]>
let patient: Observable<Patient>
}
let input: Input
let output: Output
struct Actions {
let deletePatient: Action<Patient, Void>
let togglePatient: (Patient) -> CocoaAction
let updatePatient: (Patient) -> Action<String, Void>
}
lazy var action: Actions = Actions(deletePatient: self.deletePatient,
togglePatient: self.togglePatient,
updatePatient: self.updatePatient)
// MARK: Setup
private let dependencies: Dependencies
private let patientSubject = ReplaySubject<Patient>.create(bufferSize: 1)
// MARK:- Init
init(dependencies: Dependencies) {
self.dependencies = dependencies
let sectionedPatients =
dependencies.patientService.patients()
.map { results -> [PatientSection] in
let scheduledPatients = results
.filter("checked == nil")
.sorted(byKeyPath: "created", ascending: false)
let admittedPatients = results
.filter("checked != nil")
.sorted(byKeyPath: "checked", ascending: false)
return [
PatientSection(model: "Scheduled Patients", items: scheduledPatients.toArray()),
PatientSection(model: "Admitted Patients", items: admittedPatients.toArray())
]
}
self.output = Output(sectionedPatients: sectionedPatients,
patient: patientSubject.asObservable() )
// this is immediately overriden during binding to VC - it just allows us to exit the init without errors
self.input = Input(patient: patientSubject.asObserver())
}
// MARK:- Actions
private lazy var deletePatient: Action<Patient, Void> = { (service: PatientServiceType) in
return Action { patient in
return service.delete(realmObject: patient)
}
}(self.dependencies.patientService)
lazy var togglePatient: (Patient) -> CocoaAction = { [unowned self] (patient: Patient) -> CocoaAction in
return CocoaAction {
return self.dependencies.patientService.toggle(patient: patient).map { _ in }
}
}
private lazy var updatePatient: (Patient) -> Action<String, Void> = { [unowned self] (patient: Patient) in
return Action { newName in
return self.dependencies.patientService.update(patient: patient, name: newName).map { _ in }
}
}
}