I'm quite new to reactive programming so it's still hard to me to comprehend how it works.
What I'm trying to do: when user taps on signUpButton (signUpTrigger), multiple observables are fired. I'm combining their results and producing new observable (signUp).
What happens: when user taps on signUpButton (signUpTrigger), I'm getting data from putProfileImage and createUser observables, but nothing from signUp observable. I assume it's possible to write logic to make it work, but I'm struggling to come up with it myself.
020-08-31 22:28:36.457: signUp -> subscribed
2020-08-31 22:28:36.458: createUser -> subscribed
2020-08-31 22:28:36.459: createProfileImage -> subscribed
2020-08-31 22:28:48.843: createUser -> Event next(<FIRAuthDataResult: 0x6000036fd860>)
2020-08-31 22:28:48.988: createProfileImage -> Event next(https://firebasestorage.googleapis.com/v0/b/primeval-gear-236918.appspot.com/o/profile_images%2F8173E542-93DD-4594-BAA8-705A17227F1B?alt=media&token=85c68612-bea4-41a5-a680-88a61c2e8989)
All the Viewmodel and VC code below:
final class SignUpViewModel: ViewModelType {
func transform(input: Input) -> Output {
let auth = Auth.auth()
let filename = NSUUID().uuidString
let storageRef = Storage
.storage()
.reference()
.child("profile_images")
.child(filename)
.rx
let databaseRef = Database
.database()
.reference()
.child("users")
.rx
let putProfileImage = input.signUpTrigger
.withLatestFrom(input.profileImage)
.flatMap { data in
storageRef.putData(data)
}.flatMap { _ in
storageRef.downloadURL()
}.debug("createProfileImage")
let createUser = input.signUpTrigger
.withLatestFrom(Observable.combineLatest(input.email, input.password))
.flatMap { (email,password) in
auth.rx.createUser(withEmail: email, password: password)
}.debug("createUser")
let signUp = input.signUpTrigger
.withLatestFrom(Observable.combineLatest(createUser, putProfileImage, input.username))
.flatMap { createUserResult, url, username -> Observable<DatabaseReference> in
let profileImageUrl = url.absoluteString
let uid = createUserResult.user.uid
let dictionary = ["username": username, "profileImageUrl": profileImageUrl]
let values = [uid: dictionary]
return databaseRef.updateChildValues(values)
.asObservable()
}.debug("signUp")
let canSignUp = Observable.combineLatest(input.username, input.password, input.email)
{ username, password, email in
return !username.isEmpty && !password.isEmpty && !email.isEmpty
}
return Output(canSignUp: canSignUp, isLoading: isLoading, signUp: signUp)
}
}
class SignUpViewController : UIViewController {
var disposeBag = DisposeBag()
@Injected var signUpView : SignUpView
@Injected var viewModel : SignUpViewModel
lazy var plusPhotoButton = signUpView.plusPhotoButton
lazy var signUpButton = signUpView.signUpButton
lazy var emailTextField = signUpView.emailTextField
lazy var usernameTextField = signUpView.usernameTextField
lazy var passwordTextField = signUpView.passwordTextField
override func loadView() {
view = signUpView
}
override func viewDidLoad() {
super.viewDidLoad()
setupBindings()
}
func setupBindings() {
let input = SignUpViewModel.Input(
username: usernameTextField.rx.text.orEmpty.asObservable(),
password: passwordTextField.rx.text.orEmpty.asObservable(),
email: emailTextField.rx.text.orEmpty.asObservable(),
signUpTrigger: signUpButton.rx.tap.asObservable(),
)
let output = viewModel.transform(input: input)
output.canSignUp
.bind(to:signUpButton.rx.signUpEnabled)
.disposed(by: disposeBag)
output.signUp
.bind(onNext: { result in
print(result)
}).disposed(by:disposeBag)
}
}
The way you have your code right now:
let signUp = input.signUpTrigger
.withLatestFrom(Observable.combineLatest(createUser, putProfileImage, input.username))
The updateChildValues(_:)
will happen immediately after the signUpTrigger fires, but you don't want that. You want it to happen once the results of the other three events occur.
So you want something like this:
let signUp = Observable.zip(createUser, putProfileImage, input.signUpTrigger.withLatestFrom(input.username))
.flatMap { createUserResult, url, username -> Observable<DatabaseReference> in
let profileImageUrl = url.absoluteString
let uid = createUserResult.user.uid
let dictionary = ["username": username, "profileImageUrl": profileImageUrl]
let values = [uid: dictionary]
return databaseRef.updateChildValues(values)
.asObservable()
}
.debug("signUp")