Search code examples
iosmvvmrx-swiftmoyarx-cocoa

Add a spinner on making a moya request using RxSwift and mvvm and dismiss it when user receives a response


I have an app where I am trying to implement RxSwift using MVVM.

I have the SignInViewModel where I am doing the validation and I am updating the login observable with the rest response boolean that I am listening to .

In the controller class when ever the validations pass the login button gets enabled.

In a similar manner I want to be able to start a spinner on click of the button and dismiss when the user receives a response.

When I try to listen to the loginObservable in from view model in the controller class. it does not hit the bind block.

I am not able to figure out what the problem is.

Any help will be appreciated

Following is my SignInViewModel

class SignInViewModel {

    let validatedEmail: Observable<Bool>
    let validatedPassword: Observable<Bool>
    let loginEnabled: Observable<Bool>
    let loginObservable: Observable<Bool>

    init(username: Observable<String>,
         password: Observable<String>,
         loginTap: Observable<Void>) {

        self.validatedEmail = username
            .map { $0.characters.count >= 5 }
            .shareReplay(1)

        self.validatedPassword = password
            .map { $0.characters.count >= 2 }
            .shareReplay(1)

        self.loginEnabled = Observable.combineLatest(validatedEmail, validatedPassword ) { $0 && $1 }
        let userAndPassword = Observable.combineLatest(username, password) {($0,$1)}

        self.loginObservable = loginTap.withLatestFrom(userAndPassword).flatMapLatest{ (username, password) in
            return RestService.login(username: username, password: password).observeOn(MainScheduler.instance)
        }
    }
}

Following is the moyaRequest class

final class MoyaRequest{
    func signIn(userData: Creator) -> Observable<Response> {
            return provider.request(.signIn(userData))
                .filter(statusCode: 200)
        }
}

Following is my RestService class

class RestService:NSObject {

    static var moyaRequest = MoyaRequest()
    static var disposeBag = DisposeBag()

static func login(username: String, password: String) -> Observable<Bool> {
    let userData = Creator()
    userData?.username = username
    userData?.password = password
    print("Username password", userData?.username, userData?.password)
    return  Observable.create { observer in moyaRequest.signIn(userData: userData!).subscribe{ event -> Void in
        switch event {

        case .next(let response):
            print("Response",response)

        case .error(let error):
            let moyaError: MoyaError? = error as? MoyaError
            let response: Response? = moyaError?.response
            let statusCode: Int? = response?.statusCode

            print("Sample Response code error" + String(describing: statusCode))
        default:
            break
        }
        }
        return Disposables.create()
    }

}

}

I am trying to bind the view model in the controller class.

class SignInViewController: UIViewController{

    let disposeBag = DisposeBag()
    @IBOutlet weak var passwordTextfield: UITextField!
    @IBOutlet weak var usernameTextfield: UITextField!

    private var viewModel : SignInViewModel!
    @IBOutlet weak var signInButton: UIButton!
    override func viewDidLoad() {
        setUpRxViewModel()

    }

    func setUpRxViewModel(){
        self.viewModel = SignInViewModel(username: self.usernameTextfield.rx.text.orEmpty.asObservable(),
                                         password: self.passwordTextfield.rx.text.orEmpty.asObservable(),
                                         loginTap: self.signInButton.rx.tap.asObservable())

        self.viewModel.loginEnabled.bind{ valid  in
            self.signInButton.isEnabled = valid
            }.addDisposableTo(disposeBag)

        self.viewModel.loginObservable.bind{ input in
            print("Login Clicked")
            }.addDisposableTo(disposeBag)


    }
}

Solution

  • In your login method you are not dispatching any events to your observer. It should be:

    case .next(let response):
                observer.on(.next(true))
                print("Response",response)
    
    case .error(let error):
                observer.on(.error(error))
                //or observer.on(.next(false)) if you intend to use Bool as indicator of operation success which is a very bad idea.
                let moyaError: MoyaError? = error as? MoyaError
                let response: Response? = moyaError?.response
                let statusCode: Int? = response?.statusCode
    

    furthermore I recommend you use RxMoyaProvider everywhere if you are using Moya with RxSwift. Using Observable.create usually means you are doing something wrong.

    You also shouldn't filter off events based on status code at the level of network request because if something goes wrong you are not going to receive any event in your chain.