简体   繁体   中英

rxswift viewmodel with input output

I am trying to achieve something similar in rxswift example project from RxSwift repo . But in my case there are dependent observables. I couldn't find any solution without using binding in viewmodel

Here is the structure of my viewmodel:

First the definitions of input, output and viewmodel

typealias UserListViewModelInput = (
    viewAppearAction: Observable<Void>,
    deleteAction: Observable<Int>
)

typealias UserListViewModelOutput = Driver<[User]>

typealias UserListViewModel = (UserListViewModelInput, @escaping UserApi) -> UserListViewModelOutput

Then there is actual implementation which doesn't compile.

let userListViewModel: UserListViewModel = { input, loadUsers in

    let loadedUserList = input.viewAppearAction
        .flatMapLatest { loadUsers().materialize() }
        .elements()
        .asDriver(onErrorDriveWith: .never())

    let userListAfterDelete = input.deleteAction
        .withLatestFrom(userList) { index, users in
            users.enumerated().compactMap { $0.offset != index ? $0.element : nil }
        }
        .asDriver(onErrorJustReturn: [])

    let userList = Driver.merge([loadedUserList, userListAfterDelete])

    return userList
}

Viewmodel has two job. First load the user list. Second is delete a user at index. The final output is the user list which is downloaded with UserApi minus deleted users.

The problem in here in order the define userList I need to define userListAfterDelete . And in order to define userListAfterDelete I need to define userList .

So is there a way to break this cycle without using binding inside view model? Like a placeholder observable or operator that keeps state?

This is a job for a state machine. What you will see in the code below is that there are two actions that can affect the User array. When the view appears, a new array is downloaded, when delete comes in, a particular user is removed.

This is likely the most common pattern seen in reactive code dealing with state. So common that there are whole libraries that implement some variation of it.

let userListViewModel: UserListViewModel = { input, loadUsers in

    enum Action {
        case reset([User])
        case delete(at: Int)
    }

    let resetUsers = input.viewAppearAction
        .flatMapLatest { loadUsers().materialize() }
        .compactMap { $0.element }
        .map { Action.reset($0) }

    let delete = input.deleteAction.map { Action.delete(at: $0) }

    return Observable.merge(resetUsers, delete)
        .scan(into: [User](), accumulator: { users, action in
            switch action {
            case let .reset(newUsers):
                users = newUsers
            case let .delete(index):
                users.remove(at: index)
            }
        })
        .asDriver(onErrorJustReturn: [])
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM