I'm creating an app that has a specific two-way authentication process: First, a REST based login with credentials, which returns a websocket endpoint on the server, plus an authtoken to use in order to connect to it.
Only after the websocket has successfully connected to the server, an I supposed to switch to the next view.
Trying to implement under MVVM, I have created an API struct for the server net calls:
My LoginViewController binds the username and password to the LoginViewModel, which in return binds a login Action to the login button:
func onLogin() -> CocoaAction {
return CocoaAction { _ in
self.loginService.login(username: self.usernameText.value, password: self.passwordText.value)
let MainViewModel = MainViewModel(sceneCoordinator: self.sceneCoordinator)
return self.sceneCoordinator.transition(to: Scene.mainView(mainViewModel), type: .modal).asObservable().map { _ in }
}
}
The LoginService should return a Completable for the login, in order to indicate a successful login (and move the view to the main app screen) or an error to show the user.
protocol LoginServiceType {
@discardableResult
func login(username: String, password: String) -> Completable
}
I'm having an issue with the implementation of this function. It should first call the REST login API, and after getting the response, start the connection to the websocket. The implementation for the server API is as follows (under recommended RxSwift MVVM examples):
struct Server: ServerProtocol {
// MARK: - API Errors
enum Errors: Error {
case requestFailed
}
// MARK: - API Endpoint requests
static func login(for username: String, password: String) -> Observable<JSONObject> {
let parameters = [
"username": username,
"password": password
]
return request(address: Server.Address.login, parameters: parameters)
}
// MARK: - generic request to API
static private func request<T: Any>(address: Address, parameters: [String: String] = [:]) -> Observable<T> {
return Observable.create { observer in
let request = Alamofire.request(address.url.absoluteString,
method: .post,
parameters: parameters,
encoding: JSONEncoding.default,
headers: nil)
request.responseJSON { response in
guard response.result.isSuccess == true, let data = response.data,
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? T, let result = json else {
observer.onError(Errors.requestFailed)
return
}
observer.onNext(result)
observer.onCompleted()
}
return Disposables.create {
request.cancel()
}
}
}
}
So I'm trying to figure out how to connect the REST call, its response with the need endpoint+Token, to creating the Websocket and subscribing to it's connect callback, which then should return the Completable back to the LoginViewModel.
Any advice would be welcome.
I think "flatmap" is what you're looking for.
Have a look:
var mynum = Variable(0)
let disposeBag = DisposeBag()
func login() -> Observable<(String,String)> {
return Observable.create { observer in
// Place your server access code
if (<some_condition>) { // if error
observer.on(.error(<custome error>))
}
observer.on(.next(("websocket", "authtoken")))
observer.on(.completed)
return Disposables.create()
}
}
func API(webSiteData: (String, String)) -> Observable<Int> {
return Observable.create { observer in
// Place your second server access code
print (webSiteData.0)
print (webSiteData.1)
observer.on(.next(1)) // assiging "1" just as an example, you may ignore
observer.on(.completed)
return Disposables.create()
}
}
func combine() {
self.login().catchError({ (currentError) -> Observable<(String, String)> in
print (currentError.localizedDescription)
return Observable.just(("",""))
})
.flatMap(self.API)
.bind(to: mynum)
.disposed(by: self.disposeBag)
}