Last active
April 30, 2021 08:25
-
-
Save gbasile/13901ae0e186f7a142435ba4e6bda278 to your computer and use it in GitHub Desktop.
ViewModel ViewState generation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Combine | |
public extension Publisher where Output: Equatable { | |
@discardableResult func sink() -> AnyCancellable { | |
sink( | |
receiveCompletion: { _ in /* No handling needed */}, | |
receiveValue: { _ in /* No handling needed */ }) | |
} | |
} | |
struct Device { | |
let name: String | |
} | |
struct User { | |
let name: String | |
} | |
protocol DeviceRepositoryType { | |
func query() -> AnyPublisher<[Device], Never> | |
} | |
protocol UserRepositoryType { | |
func query() -> AnyPublisher<User, Never> | |
func update(name: String) -> AnyPublisher<Never, Error> | |
} | |
enum ViewState: Equatable { | |
struct Content: Equatable { | |
let title: String | |
let message: String | |
} | |
case loading | |
case error | |
case content(Content) | |
} | |
class ViewModel { | |
fileprivate enum ActionState { | |
case loading | |
case idle | |
case error | |
} | |
private let userRepository: UserRepositoryType | |
private let deviceRepository: DeviceRepositoryType | |
private let actionState = CurrentValueSubject<ActionState, Never>(.idle) | |
private var disposables = Set<AnyCancellable>() | |
init(userRepository: UserRepositoryType, deviceRepository: DeviceRepositoryType) { | |
self.userRepository = userRepository | |
self.deviceRepository = deviceRepository | |
} | |
func viewState() -> AnyPublisher<ViewState, Never> { | |
Publishers.CombineLatest3( | |
userRepository.query(), | |
deviceRepository.query(), | |
actionState | |
) | |
.map(toViewState) | |
.eraseToAnyPublisher() | |
} | |
func update(userName: String) { | |
userRepository.update(name: userName) | |
.handleEvents( | |
receiveSubscription: { _ in | |
self.actionState.send(.loading) | |
}, | |
receiveCompletion: { completion in | |
switch completion { | |
case .finished: | |
self.actionState.send(.idle) | |
case .failure: | |
self.actionState.send(.error) | |
} | |
}) | |
.sink() | |
.store(in: &disposables) | |
} | |
} | |
private func toViewState(user: User, devices: [Device], actionState: ViewModel.ActionState) -> ViewState { | |
if actionState == .loading { | |
return .loading | |
} | |
if actionState == .error { | |
return .error | |
} | |
let content = ViewState.Content( | |
title: "Hello \(user.name)", | |
message: "You have \(devices.count) on your profile" | |
) | |
return .content(content) | |
} |
It seems above snippet needs to tie lifecycle of effect with lifecycle of VM.
VM needs to add
var disposables = Set<AnyCancellable>()
and at the call side
userRepository.update(name: userName)
.handleEvents(....)
.sink()
.store(in: &disposables)
and when VM gets deallocated, the handle to effect gets purged too.
Yes, I wanted to remove at maximum everything that is was not really relevant for the ViewState creation, but as we want to use this code as a reference for our documentation it might be better to be explicit
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
💪