5

Sto facendo un semplice modulo di accesso (e-mail e password) per cercare di rafforzare il mio skillset di programmazione reattiva. Sto avendo qualche problema a far funzionare la convalida del campo email nel modo in cui lo voglio.Utilizzando RxJava per la convalida dell'accesso alla posta elettronica, un osservabile emette due volte

Ecco il mio codice:

final Observable<CharSequence> email = RxTextView.textChanges(emailView); 

    Observable<Boolean> emailIsValid = email.map(new Func1<CharSequence, Boolean>() { 
     @Override 
     public Boolean call(CharSequence charSequence) { 
      Log.d("asdf", "emailIsValid call: " + charSequence); 
      return Pattern.matches(Patterns.EMAIL_ADDRESS.pattern(), charSequence); 
     } 
    }); 
    RxView.focusChanges(emailView) 
      .withLatestFrom(emailIsValid, new Func2<Boolean, Boolean, Boolean>() { 
       @Override 
       public Boolean call(Boolean hasFocus, Boolean emailIsValid) { 
        return (!hasFocus && !emailIsValid); 
       } 
      }) 
      .subscribe(new Action1<Boolean>() { 
       @Override 
       public void call(Boolean showError) { 
        if (showError) { 
         emailInputLayout.setError("Enter a valid email"); 
        } else { 
         emailInputLayout.setError(null); 
        } 
       } 
      }); 
    Observable<CharSequence> password = RxTextView.textChanges(passwordView); 

    Observable.combineLatest(emailIsValid, password, 
      new Func2<Boolean, CharSequence, Boolean>() { 
       @Override 
       public Boolean call(Boolean emailIsValid, CharSequence password) { 
        Log.d("asdf", "valid: " + emailIsValid + ", password: " + password); 
        return (emailIsValid && password.length() > 0); 
       } 
      }) 
      .subscribe(RxView.enabled(loginButton)); 

Ed ecco il registro:

emailIsValid call: emailIsValid call: valid: false, password: // I type 'j' emailIsValid call: j emailIsValid call: j valid: false, password: // I type 'a' emailIsValid call: ja emailIsValid call: ja valid: false, password:

Come si può vedere, emailIsValid è chiamato due volte ogni volta che si digita un carattere, il che significa che sta facendo un regex match due volte, che è una specie di spreco.

ho guardato come avrei potuto fare emailIsValid chiamare una sola volta per il cambiamento, non importa quanti abbonati ha, e ho trovato il metodo share(). Ecco cosa accade quando aggiungo .share() alla fine della dichiarazione emailIsValid s':

emailIsValid call: // I type 'j' emailIsValid call: j valid: false, password: // I type 'a' emailIsValid call: ja valid: false, password:

che risolve il problema, ma provoca un altro: non c'è emettono iniziale emailIsValid alla funzione combineLatest alla fine, quindi il pulsante Login viene abilitato, quando deve essere disattivato (visualizzato in grigio).

Qual è il modo più pulito per risolvere questo problema? I penso Voglio che si comporti come un BehaviorSubject, ma non sono sicuro se questo è il modo migliore per farlo.

risposta

1

È possibile utilizzare publish() e connect().

val email = RxTextView.textChanges(emailEditText) 
val emailIsValid = email.map { charSequence -> 
    Log.d("asdf", "emailIsValid call: " + charSequence) 
    Pattern.matches(Patterns.EMAIL_ADDRESS.pattern(), charSequence) 
}.publish() 
RxView.focusChanges(emailEditText) 
    .withLatestFrom(emailIsValid) { hasFocus, emailIsValid -> 
     (!hasFocus && !emailIsValid) 
    } 
    .subscribe { showError -> 
     if (showError) { 
     Log.d("asdf", "error") 
     } 
    } 
val password = RxTextView.textChanges(passwordEditText) 
Observable.combineLatest(emailIsValid, password) { emailIsValid, password -> 
    Log.d("asdf", "valid: $emailIsValid, password: $password") 
    (emailIsValid && password.length > 0) 
}.subscribe(RxView.enabled(button)) 
emailIsValid.connect() 

O semplicemente cambiare l'ordine della vostra subscribe perché provoca il problema.

val email = RxTextView.textChanges(emailEditText) 
val emailIsValid = email.map { charSequence -> 
    Log.d("asdf", "emailIsValid call: " + charSequence) 
    Pattern.matches(Patterns.EMAIL_ADDRESS.pattern(), charSequence) 
}.share() 
val password = RxTextView.textChanges(passwordEditText) 
Observable.combineLatest(emailIsValid, password) { emailIsValid, password -> 
    Log.d("asdf", "valid: $emailIsValid, password: $password") 
    (emailIsValid && password.length > 0) 
}.subscribe(RxView.enabled(button)) 
RxView.focusChanges(emailEditText) 
    .withLatestFrom(emailIsValid) { hasFocus, emailIsValid -> 
     (!hasFocus && !emailIsValid) 
    } 
    .subscribe { showError -> 
     if (showError) { 
     Log.d("asdf", "error") 
     } 
    } 

Nota: il codice è in Kotlin e ulteriori informazioni sulla pubblicazione/Connect è in http://www.introtorx.com/content/v1.0.10621.0/14_HotAndColdObservables.html#PublishAndConnect

Il tuo problema è simile a quello che c'è menzione nella sezione PublishAndConnect .:

Il secondo abbonamento sottoscrive in ritardo e manca la prima pubblicazione. Potremmo spostare l'invocazione del metodo Connect() fino al termine di tutte le sottoscrizioni. In questo modo, anche con la chiamata a Thread.Sleep, non sottoscriveremo realmente il sottostante fino a quando non verranno effettuate entrambe le sottoscrizioni.

+0

Impressionante, non era a conoscenza di osservabili collegabili. Funziona bene, grazie! –

+0

@ D_Steve595 NP. Sono contento di poterti aiutare! – pt2121

1

Io penso quello che sta succedendo qui è la seguente:

  • La prima subscribe - quello alla fine del RxView.focusChange()... - provoca un abbonamento al emailIsValid (e quindi anche a email).

  • email verrà poi emettere immediatamente il contenuto corrente del TextView come primo elemento, che a sua volta passa attraverso emailIsValid e share e al primo Subscriber (i. E. L'operatore withLatestFrom).

  • Qualche tempo dopo, lo combineLatest causa un'altra sottoscrizione a emailIsValid. Poiché emailIsValid è share d, questo abbonamento non "passa" a email e pertanto ogni elemento verrà comunque emesso una sola volta.

  • Il problema è ora che si comporta come un sharePublishSubject: E 'appena emette eventuali future eventi a tutti gli abbonati, ma non riprodurre alcun di quelli del passato.

In sintesi questo significa: Quando il secondo Subscriber (la combineLatest) arriva, il valore iniziale è già passato - è stato emesso subito dopo la prima sottoscrizione. E il prossimo valore arriverà solo quando si modifica il contenuto dello TextView.

Soluzione: Prova replay(1).refCount() invece share() alla fine di emailIsValid - che dovrebbe assicurare che ogni nuovo abbonato riceve anche l'ultimo precedente risultato della valutazione, così come tutti quelli futuri.

Spero che questo risolva il problema e che la mia spiegazione abbia un senso.