/
GithubSignupViewModel2.swift
123 lines (99 loc) · 4.11 KB
/
GithubSignupViewModel2.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//
// GithubSignupViewModel2.swift
// RxExample
//
// Created by Krunoslav Zaher on 12/6/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import RxSwift
import RxCocoa
/**
This is example where view model is mutable. Some consider this to be MVVM, some consider this to be Presenter,
or some other name.
In the end, it doesn't matter.
If you want to take a look at example using "immutable VMs", take a look at `TableViewWithEditingCommands` example.
This uses Driver builder for sequences.
Please note that there is no explicit state, outputs are defined using inputs and dependencies.
Please note that there is no dispose bag, because no subscription is being made.
*/
class GithubSignupViewModel2 {
// outputs {
//
let validatedUsername: Driver<ValidationResult>
let validatedPassword: Driver<ValidationResult>
let validatedPasswordRepeated: Driver<ValidationResult>
// Is signup button enabled
let signupEnabled: Driver<Bool>
// Has user signed in
let signedIn: Driver<Bool>
// Is signing process in progress
let signingIn: Driver<Bool>
// }
init(
input: (
username: Driver<String>,
password: Driver<String>,
repeatedPassword: Driver<String>,
loginTaps: Signal<()>
),
dependency: (
API: GitHubAPI,
validationService: GitHubValidationService,
wireframe: Wireframe
)
) {
let API = dependency.API
let validationService = dependency.validationService
let wireframe = dependency.wireframe
/**
Notice how no subscribe call is being made.
Everything is just a definition.
Pure transformation of input sequences to output sequences.
When using `Driver`, underlying observable sequence elements are shared because
driver automagically adds "shareReplay(1)" under the hood.
.observe(on:MainScheduler.instance)
.catchAndReturn(.Failed(message: "Error contacting server"))
... are squashed into single `.asDriver(onErrorJustReturn: .Failed(message: "Error contacting server"))`
*/
validatedUsername = input.username
.flatMapLatest { username in
return validationService.validateUsername(username)
.asDriver(onErrorJustReturn: .failed(message: "Error contacting server"))
}
validatedPassword = input.password
.map { password in
return validationService.validatePassword(password)
}
validatedPasswordRepeated = Driver.combineLatest(input.password, input.repeatedPassword, resultSelector: validationService.validateRepeatedPassword)
let signingIn = ActivityIndicator()
self.signingIn = signingIn.asDriver()
let usernameAndPassword = Driver.combineLatest(input.username, input.password) { (username: $0, password: $1) }
signedIn = input.loginTaps.withLatestFrom(usernameAndPassword)
.flatMapLatest { pair in
return API.signup(pair.username, password: pair.password)
.trackActivity(signingIn)
.asDriver(onErrorJustReturn: false)
}
.flatMapLatest { loggedIn -> Driver<Bool> in
let message = loggedIn ? "Mock: Signed in to GitHub." : "Mock: Sign in to GitHub failed"
return wireframe.promptFor(message, cancelAction: "OK", actions: [])
// propagate original value
.map { _ in
loggedIn
}
.asDriver(onErrorJustReturn: false)
}
signupEnabled = Driver.combineLatest(
validatedUsername,
validatedPassword,
validatedPasswordRepeated,
signingIn
) { username, password, repeatPassword, signingIn in
username.isValid &&
password.isValid &&
repeatPassword.isValid &&
!signingIn
}
.distinctUntilChanged()
}
}