5

Tworzę prosty formularz logowania (e-mail i hasło), aby wzmocnić mój zestaw umiejętności programowania reaktywnego. Mam problem z uzyskaniem sprawdzania poprawności adresu e-mail, aby działał tak, jak chcę.Używanie RxJava do sprawdzania poprawności logowania przez e-mail, obserwowalny emituje dwa razy

Oto mój kod:

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)); 

A oto log:

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:

Jak widać, emailIsValid nazywany jest dwa razy za każdym razem wpisuję charakter, co oznacza, że ​​to robi regex match dwa razy, co jest trochę nieekonomiczne.

Sprawdziłem, jak mogę wykonać emailIsValid dzwonić tylko raz na zmianę, bez względu na to, ilu abonentów ma, i znalazłem metodę share(). Oto co się dzieje, gdy dodam .share() do końca deklaracji emailIsValid „s:

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

To rozwiązuje problem, ale powoduje inny: Nie ma początkowy emitować przez emailIsValid do funkcji combineLatest na końcu, więc przycisk logowania jest włączony, kiedy powinien być wyłączony (wyszarzony).

Jaki jest najczystszy sposób rozwiązania tego problemu? I think Chcę, aby zachowywał się jak BehaviorSubject, ale nie jestem pewien, czy to najlepszy sposób to zrobić.

Odpowiedz

1

Można użyć publish() i 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() 

Albo po prostu przełączyć kolejność subscribe ponieważ powoduje ten problem.

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") 
     } 
    } 

Uwaga: kod jest w Kotlin i więcej informacji na temat publikowania/połączyć się przy http://www.introtorx.com/content/v1.0.10621.0/14_HotAndColdObservables.html#PublishAndConnect

Twój problem jest podobny do tego, co nie wspomnieć w sekcji PublishAndConnect .:

drugim subskrypcja subskrybuje się późno i pomija pierwszą publikację. Możemy przenieść wywołanie metody Connect(), dopóki wszystkie subskrypcje nie zostaną wykonane. W ten sposób, nawet z wywołaniem Thread.Sleep, nie będziemy mogli zasubskrybować bazy, dopóki nie zostaną wykonane obie subskrypcje.

+0

Niesamowite, nie wiedział o możliwych do zaobserwowania sprzężeniach. Działa dobrze, dziękuję! –

+0

@ D_Steve595 NP. Cieszę się, że mogę pomóc! – pt2121

1

myślę co się dzieje tutaj jest następujący:

  • Pierwszy subscribe - jedno na koniec RxView.focusChange()... - powoduje subskrypcję emailIsValid (a zatem również do email).

  • email wtedy natychmiast emituje aktualną treść TextView jako jej pierwszy element, który z kolei przechodzi przez emailIsValid i share i na pierwszy Subscriber (tj. Np. Operatorowi withLatestFrom).

  • Jakiś czas później combineLatest powoduje kolejną subskrypcję na emailIsValid. Ponieważ emailIsValid jest share d, niniejsza Subskrypcja nie "przechodzi" do email i dlatego każdy element będzie emitowany tylko raz.

  • Problem jest teraz, że share zachowuje się jak PublishSubject: To właśnie emituje przyszłych wydarzenia dla wszystkich abonentów, ale nie powtarzać żadnej z tych ostatnich.

W sumie oznacza to: Po drugie Subscriber (the combineLatest) przybywa, początkowa wartość to już przeszłość - to był emitowany tuż po pierwszej subskrypcji. Następna wartość pojawi się tylko wtedy, gdy zmienisz treść zawartość .

Rozwiązanie: Spróbuj replay(1).refCount() zamiast share() na koniec emailIsValid - które powinny zapewnić, że każdy nowy abonent otrzymuje również ostatni poprzedni wynik oceny, jak również wszystkich przyszłych.

Mam nadzieję, że rozwiąże to problem i że moje wyjaśnienie ma sens.