Todos nós já estivemos nesta situação: projeto novo, aplicativo novo. E uma tela de login.
Muitas vezes, é nesse momento que nos questionamos sobre as decisões mais básicas em relação à arquitetura e à organização do código que vamos construir a partir desse momento. Se pararmos para pensar, a tela de login agrega e unifica várias faces do desenvolvimento de software: conexão com uma API, armazenamento (seguro) de informações do usuário, validação de dados, interação com o usuário e até bons cenários de testes.
E por isso é sempre uma ótima oportunidade para tentarmos, testarmos – e muitas vezes aprendermos – coisas novas. Eu acredito que uma tela de login oferece desafios para desenvolvedores de todos os níveis de experiência.
A tela de login
Nome de usuário, senha e um botão.
O cenário não poderia ser mais ideal para exercitarmos o uso de bindings. A ideia é simples: queremos habilitar o botão se os campos de nome de usuário e senha forem válidos. Por enquanto, vamos supor que ambos os campos são válidos se tiverem pelo menos um caractere.
Nenhuma novidade por aqui. Apenas por clareza, estes são nossos IBOutlets:
@IBOutlet private weak var usernameTextField: LoginTextField! @IBOutlet private weak var passwordTextField: LoginTextField! @IBOutlet private weak var loginButton: UIButton!
E, como em qualquer mundo, vamos definir as nossas duas funções de validação:
func validateUsername(username: String) -> Bool { return username.characters.count > 0 } func validatePassword(password: String) -> Bool { return password.characters.count > 0 }
E, agora, vamos fazer nosso binding:
let validUsername = usernameTextField.rx_text.map(validateUsername) let validPassword = passwordTextField.rx_text.map(validatePassword) [validUsername, validPassword] .combineLatest { $0.first! && $0.last! } .bindTo(loginButton.rx_enabled) .addDisposableTo(disposeBag)
É exatamente isto: essas seis linhas de código resolvem o nosso problema.
Se você não tem ideia do que o código acima significa, recomendo ler o meu artigo sobre RxSwift.
Primeiramente, é importante saber que existe uma extensão do RxSwift chamada RxCocoa que aplica os conceitos do RxSwift em várias classes do Cocoa/UIKit. É por meio do RxCocoa que conseguimos fazer os bindings.
No código acima, fazemos um map em cima do rx_text (que nada mais é do que o stream de strings do UITextField. Lembre-se: no RxSwift, tudo são streams). E as funções que utilizamos para fazer o map nada mais são do que as nossas funções de validação declaradas mais acima.
Com isso, nossas variáveis validUsername e validPassoword são do tipo Observable<Bool> e não Bool, o que nos permite usar o operador combineLatest, que emitirá um array com dois Bool toda vez que um dos streams validUsername ou validPassword emitirem um valor. E esses dois streams, por sua vez, irão emitir valores quando houver alguma mudança no usernameTextField e passwordTextField, respectivamente.
O nosso combineLatest nada mais faz do que um && entre o primeiro e o último elemento do nosso array. Aqui usamos um force unwrap (é, eu sei: dói até de ver) porque sabemos que nosso array terá sempre dois elementos.
E agora? Qual o tipo do resultado do resultado do nosso combineLatest? Se você pensou Observable<Bool>, já está pensando de uma maneira mais zen ☮️.
Por último (ou quase último), fazemos o bind desse resultado com o valor rx_enabled do nosso loginButton. Ou seja, toda vez que o resultado do combineLatest for true, nosso botão irá passar para o estado ativo. E toda vez que o resultado for false, nosso botão ficará inativo.
Por último (de verdade), temos o addDisposableTo. Essa é a forma como o RxSwift gerencia a memória e os recursos alocados. Se você quiser saber mais sobre as DisposeBags, recomenso ler o Getting Started do RxSwift.
No próximo artigo, vamos trabalhar em cima de outro conceito simples, mas com uma abordagem funcional: vamos fazer com que a ação do nosso botão de login dispare a requisição de autenticação para a nossa API.