iOS Tutorial - The standard login screen but in rx and mvvm !



In this tutorial we gonna explain how mvvm can be used along with rx










Model . (M)
Model refers either to a domain model, which represents real state content (an object-oriented approach), or to the data access layer, which represents content (a data-centric approach).[citation needed]
View (V)
As in the model-view-controller (MVC) and model-view-presenter (MVP) patterns, the view is the structure, layout, and appearance of what a user sees on the screen.[6] It displays a representation of the model and receives the user's interaction with the view (clicks, keyboard, gestures, etc.), and it forwards the handling of these to the view model via the data binding (properties, event callbacks, etc.) that is defined to link the view and view model.
View model (VM)
The view model is an abstraction of the view exposing public properties and commands. Instead of the controller of the MVC pattern, or the presenter of the MVP pattern, MVVM has a binder, which automates communication between the view and its bound properties in the view model. The view model has been described as a state of the data in the model.[7]
The main difference between the view model and the Presenter in the MVP pattern, is that the presenter has a reference to a view whereas the view model does not. Instead, a view directly binds to properties on the view model to send and receive updates. To function efficiently, this requires a binding technology or generating boilerplate code to do the binding.[6]

In computingreactive programming is a declarative programming paradigm concerned with data streams and the propagation of change. With this paradigm it is possible to express static (e.g., arrays) or dynamic (e.g., event emitters) data streams with ease, and also communicate that an inferred dependency within the associated execution model exists, which facilitates the automatic propagation of the changed data flow.[citation needed]


an implementation of rx for swift 



You can think about the view model as the logic blackbox that receive inputs and output results.
In our case using rx it receives "streams  of inputs" and produces streams of outputs 
something like "Reacting to actions"


MVVM tries to separate the logic  from the controller and make it "Testable" so you don't have to complicate things to test something 




Before we begin our tutorial you have to put one thing in your mind :D "This is An implementation combine rx with mvvm "
You can think in other implementation or find a better one for it 



First step : 

create an new Xcode project and make sure that its hierarchy look like this:

1- protocols : hold all the protocols which needs to be implemented 
2- vc login : login view controller which will hold ref to login view model 
3- vm login : The login view model which will contains many login services including for ex facebook , native login for application and gmail, etc. 
4- native login service : the network logic for the native login service 




Second step :
storyboard configuration :
we have only one screen , the login screen its so simple because we focus on the pattern more:



Third Step:

The UIStackView constraints 






4th step: 

Creating the protocols:

the ViewModel protocol : 


protocol ViewModelProtocol: class {
    associatedtype Input
    associatedtype Output
}




This means every view model will have these types of variables Input and Output


The controller protocol: 


protocol ControllerType : class {
    associatedtype ViewModelType: ViewModelProtocol
    
    func configure(with viewModel: ViewModelType)
    /// Factory function for view controller instatiation
    ///
    /// - Parameter viewModel: View model object
    /// - Returns: View controller of concrete type
    static func create(with viewModel: ViewModelType) -> UIViewController
}


it has function to create a view controller binds to a view model
(this helps if you designing views by code you may have to instantiate the view controller an present it )

and another function to configure it if you using the storyboards


ViewModelType is a a type must conformed to, and must extend ViewModelProtocol





5th step : 

The Login view model:
preparing the inputs and outputs:

We need an email ? maybe password also the sign tap event

class LoginInput {
    
    let email: BehaviorSubject<String>
    let password: BehaviorSubject<String>
    let signInDidTap: PublishSubject<Void>
    
    init() {
        email = BehaviorSubject<String>(value: "")
        password = BehaviorSubject<String>(value: "")
        signInDidTap = PublishSubject<Void>()
    }
}

the login output we need to observe the login result (user in this case) and errors 

class LoginOutput {
    let loginResultObservable: PublishSubject<User>
    let errorsObservable: PublishSubject<Error>
    init() {
        loginResultObservable = PublishSubject<User>()
        errorsObservable = PublishSubject<Error>()
    }
}



The view model itself: 

class LoginViewModel : ViewModelProtocol {
    typealias Input = LoginInput
    
    typealias Output = LoginOutput
    
    let input : Input
    let output: Output

    let disposeBag = DisposeBag()
    var loginServices : [String : LoginService]  = [:]
    
    func addLoginService (loginService : LoginService , name : String) {
        loginServices[name] = loginService
    }

    init() {
        input = Input()
        output = Output()
        
        input.signInDidTap
            .subscribe({_  in 
                self.loginServices["native"]?
                    .signIn(userName:  try! self.input.email.value() ,
                            password: try! self.input.password.value())
                    .subscribe(onNext: {
                        (user) in
                        self.output.loginResultObservable.onNext(user)
                    }, onError: {
                        (err) in
                        self.output.errorsObservable.onNext(err)
                    }).disposed(by: self.disposeBag)
            })
            .disposed(by: disposeBag)
    }
}


You must confirm to ViewModelProtocol to tell him what types that confirms to LoginInput and LoginOutput
we use typealias to do this 


in init function we just initialize the input and output with its dummy values 
then subscribe to sign in tap  to call sign in api and pass the outputs for it to the view model outputs 



Implementation of native login service: 

enum Authentication : Error {
   case invalidCredintials (String)
}

class NativeLoginService : LoginService {
    var users =   [ "user0" : "12345678" ,
                    "user1" : "12345678",
                    "user2" : "951753" ]
    
    func signIn(userName: String, password: String) -> Observable<User> {
        return Observable.create { observer in
            if let exsitUser =  self.users.first(where: { (arg0) -> Bool in
                
                let (key, value) = arg0
                return (userName == key) && (password == value)
            }) {
                observer.onNext(User(name : exsitUser.key))
            }
            else {
                observer.onError(Authentication.invalidCredintials("INVALID USER NAME OR PASSWORD PLEASE TRY LATER"))
            }
            
            return Disposables.create()
        }
    }
}


simulating the network logic by creating a key value map holds some users



6th step:
creating the login view controller it self


class VCLogin  : UIViewController , ControllerType {


    @IBOutlet weak var passwordTextfield: UITextField!
    @IBOutlet weak var emailTextfield: UITextField!
    @IBOutlet weak var signInButton: UIButton!
    typealias ViewModelType = LoginViewModel
    var viewModel : ViewModelType!
    let disposeBag = DisposeBag()
    override func viewDidLoad() {
        super.viewDidLoad()
        configure(with: viewModel)

    }
    
    func presentError(_ e:Error? ) {
        print(e ?? "")
        presentMessage(e?.localizedDescription ?? "")
    }
    
    func presentMessage(_ msg : String) {
        let alert =  UIAlertController(title: "Login", message: msg, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
        present(alert, animated: true, completion: nil)
    }
}


create an instance of it 

extension VCLogin {
    static func create(with viewModel: ViewModelType) -> UIViewController {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let controller = storyboard.instantiateViewController(withIdentifier: "VCLogin") as! VCLogin
        controller.viewModel = viewModel
        return controller
    }
}



and this function to configure and connect the UIElements to associated input and output 
think about wiring it up with the view model cables 

func configure(with viewModel: LoginViewModel) {
        
// wiring the email text field to viewmodel email input 

        emailTextfield.rx.text.orEmpty
        .subscribe(viewModel.input.email)
        .disposed(by: disposeBag)

        // the same 

        passwordTextfield.rx.text.orEmpty
        .subscribe(viewModel.input.password)
        .disposed(by: disposeBag)
        
        signInButton.rx.tap.asObservable()
            .subscribe(viewModel.input.signInDidTap)
            .disposed(by: disposeBag)

         viewModel.output.errorsObservable
            .subscribe({ [unowned self] (error) in
                self.presentError(error.element)
            })
            .disposed(by: disposeBag)

        viewModel.output.loginResultObservable
            .subscribe({ [unowned self] (user) in
                self.presentMessage("User successfully signed in \(user.element?.name ?? "")")
            })
            .disposed(by: disposeBag)
        
    }






Now we have completed the tutorial resulting a clean implementation of login screen with rx and mvvm pattern! 



refs :


Comments

Popular posts from this blog

iOS Tutorial - Twitter profile view controller

iOS Tutorial - Easy and flexible custom fonts in the runtime without so much code !