2016-09-04 37 views
12

Mam formularz, który może mieć zmienną liczbę EditText, który musi zostać sprawdzony przed przesłaniem formularza. Mogę wykonać sprawdzanie poprawności, jeśli EditText s są ustalone w liczbie jak po -RxJava-RxAndroid formularz sprawdzania poprawności dynamicznej EditText

Observable<CharSequence> emailObservable = RxTextView.textChanges(editEmail).skip(1); 
Observable<CharSequence> passwordObservable = RxTextView.textChanges(editPassword).skip(1); 

mFormValidationSubscription = Observable.combineLatest(emailObservable, passwordObservable, 
       (newEmail, newPassword) -> {     
        boolean emailValid = !TextUtils.isEmpty(newEmail) && android.util.Patterns.EMAIL_ADDRESS.matcher(newEmail).matches(); 
        if(!emailValid) { 
         emailInputLayout.setError(getString(R.string.error_invalid_email)); 
         emailInputLayout.setErrorEnabled(true); 
        }else { 
         emailInputLayout.setError(null); 
         emailInputLayout.setErrorEnabled(false); 
        } 

        boolean passValid = !TextUtils.isEmpty(newPassword) && newPassword.length() > 4; 
        if (!passValid) { 
         passwordInputLayout.setError(getString(R.string.error_invalid_password)); 
         passwordInputLayout.setErrorEnabled(true); 
        } else { 
         passwordInputLayout.setError(null); 
         passwordInputLayout.setErrorEnabled(true); 
        } 

        return emailValid && passValid; 
       }).subscribe(isValid ->{ 
        mSubmitButton.setEnabled(isValid); 
       }); 

Ale teraz jak istnieje zmienna liczba wejść próbowałem tworzenie listy Observable<CharSequence> i Observable.combineLatest() ale utknąłem jak postępować z tym .

List<Observable<CharSequence>> observableList = new ArrayList<>(); 

     for(InputRule inputRule : mMaterial.getRules()) { 
      View vInputRow = inflater.inflate(R.layout.item_material_input_row, null, false); 

      StyledEditText styledEditText = ((StyledEditText)vInputRow.findViewById(R.id.edit_input)); 
      styledEditText.setHint(inputRule.getName()); 

      Observable<CharSequence> observable = RxTextView.textChanges(styledEditText).skip(1); 
      observableList.add(observable); 

      linearLayout.addView(vInputRow); 
     } 

     Observable.combineLatest(observableList,......); // What should go in place of these "......" 

Jak mogę przeprowadzić kontrole na ważnej charsequence dla każdego pola wejściowego. Spojrzałem na metody flatMap(), map(), filter(), ale nie wiem, jak z nich korzystać.

+0

Można to zrobić za pomocą poprzez zapisanie każdego z zaobserwować, że chcesz. Zwróciłby ci sekwencję znaków i mapując operatora funkcji, mógłbyś sprawdzić poprawność danych wejściowych. Daj mi znać, jeśli nie możesz tego zrozumieć. –

+0

Czy to było pomocne? –

Odpowiedz

5

Tak, można przetwarzać liczby abitrary obserwabli w .combineLatest(), ale nadal istnieje obejście. Zainteresowałem się tym problemem i wymyśliłem następujące rozwiązanie - możemy przechowywać informacje o niektórych źródłach danych - ostatniej wartości i źródłowym identyfikatorze (łańcuch i identyfikator zasobu) i tunelować wszystkie dane do pewnej wspólnej rury. Do tego możemy użyć PublishSubject. Musimy również śledzić stan połączenia, ponieważ powinniśmy zapisać subskrypcję każdego źródła w ramach subskrypcji i odciąć ją, gdy zrezygnujemy z subskrypcji z tego źródła. Przechowujemy ostatnie dane z każdego źródła, abyśmy mogli powiedzieć użytkownikowi, które źródło właśnie wysłało nową wartość, wywołanie zwrotne będzie zawierać tylko identyfikator źródłowy. Użytkownik może uzyskać ostatnią wartość dowolnego źródła według identyfikatora źródła. wpadłem na następujący kod:

import android.util.Log; 
import android.widget.EditText; 

import com.jakewharton.rxbinding.widget.RxTextView; 

import java.util.ArrayList; 
import java.util.HashMap; 
import java.util.Map; 
import rx.Observable; 
import rx.Subscription; 
import rx.functions.Action1; 
import rx.subjects.PublishSubject; 

public class MultiSourceCombinator { 
    String LOG_TAG = MultiSourceCombinator.class.getSimpleName(); 
    /** 
    * We can't handle arbitrary number of sources by CombineLatest, but we can pass data along 
    * with information about source (sourceId) 
    */ 
    private static class SourceData{ 
     String data = ""; 
     Integer sourceId = 0; 
    } 

    /** 
    * Keep id of source, subscription to that source and last value emitted 
    * by source. This value is passed when source is attached 
    */ 
    private class SourceInfo{ 
     Subscription sourceTracking; 
     Integer sourceId; 
     SourceData lastData; 

     SourceInfo(int sourceId, String data){ 
      this.sourceId = sourceId; 
      // initialize last data with empty value 
      SourceData d = new SourceData(); 
      d.data = data; 
      d.sourceId = sourceId; 
      this.lastData = d; 
     } 
    } 

    /** 
    * We can tunnel data from all sources into single pipe. Subscriber can treat it as 
    * Observable<SourceData> 
    */ 
    private PublishSubject<SourceData> dataDrain; 

    /** 
    * Stores all sources by their ids. 
    */ 
    Map<Integer, SourceInfo> sources; 

    /** 
    * Callback, notified whenever source emit new data. it receives source id. 
    * When notification is received by client, it can get value from source by using 
    * getLastSourceValue(sourceId) method 
    */ 
    Action1<Integer> sourceUpdateCallback; 

    public MultiSourceCombinator(){ 
     dataDrain = PublishSubject.create(); 
     sources = new HashMap<>(); 
     sourceUpdateCallback = null; 
     // We have to process data, ccoming from common pipe 
     dataDrain.asObservable() 
       .subscribe(newValue -> { 
        if (sourceUpdateCallback == null) { 
         Log.w(LOG_TAG, "Source " + newValue.sourceId + "emitted new value, " + 
           "but used did't set callback "); 
        } else { 
         sourceUpdateCallback.call(newValue.sourceId); 
        } 
       }); 
    } 

    /** 
    * Disconnect from all sources (sever Connection (s)) 
    */ 
    public void stop(){ 
     Log.i(LOG_TAG, "Unsubscribing from all sources"); 
     // copy references to aboid ConcurrentModificatioinException 
     ArrayList<SourceInfo> t = new ArrayList(sources.values()); 
     for (SourceInfo si : t){ 
      removeSource(si.sourceId); 
     } 
     // right now there must be no active sources 
     if (!sources.isEmpty()){ 
      throw new RuntimeException("There must be no active sources"); 
     } 
    } 

    /** 
    * Create new source from edit field, subscribe to this source and save subscription for 
    * further tracking. 
    * @param editText 
    */ 
    public void addSource(EditText editText, int sourceId){ 
     if (sources.containsKey(sourceId)){ 
      Log.e(LOG_TAG, "Source with id " + sourceId + " already exist"); 
      return; 
     } 
     Observable<CharSequence> source = RxTextView.textChanges(editText).skip(1); 
     String lastValue = editText.getText().toString(); 
     Log.i(LOG_TAG, "Source with id " + sourceId + " has data " + lastValue); 
     // Redirect data coming from source to common pipe, to do that attach source id to 
     // data string 
     Subscription sourceSubscription = source.subscribe(text -> { 
      String s = new String(text.toString()); 
      SourceData nextValue = new SourceData(); 
      nextValue.sourceId = sourceId; 
      nextValue.data = s; 
      Log.i(LOG_TAG, "Source " + sourceId + "emits new value: " + s); 
      // save vlast value 
      sources.get(sourceId).lastData.data = s; 
      // pass new value down pipeline 
      dataDrain.onNext(nextValue); 
     }); 
     // create SourceInfo 
     SourceInfo sourceInfo = new SourceInfo(sourceId, lastValue); 
     sourceInfo.sourceTracking = sourceSubscription; 
     sources.put(sourceId, sourceInfo); 
    } 

    /** 
    * Unsubscribe source from common pipe and remove it from list of sources 
    * @param sourceId 
    * @throws IllegalArgumentException 
    */ 
    public void removeSource(Integer sourceId) throws IllegalArgumentException { 
     if (!sources.containsKey(sourceId)){ 
      throw new IllegalArgumentException("There is no source with id: " + sourceId); 
     } 
     SourceInfo si = sources.get(sourceId); 
     Subscription s = si.sourceTracking; 
     if (null != s && !s.isUnsubscribed()){ 
      Log.i(LOG_TAG, "source " + sourceId + " is active, unsubscribing from it"); 
      si.sourceTracking.unsubscribe(); 
      si.sourceTracking = null; 
     } 
     // source is disabled, remove it from list 
     Log.i(LOG_TAG, "Source " + sourceId + " is disabled "); 
     sources.remove(sourceId); 
    } 

    /** 
    * User can get value from any source by using source ID. 
    * @param sourceId 
    * @return 
    * @throws IllegalArgumentException 
    */ 
    public String getLastSourceValue(Integer sourceId) throws IllegalArgumentException{ 
     if (!sources.containsKey(sourceId)){ 
      throw new IllegalArgumentException("There is no source with id: " + sourceId); 
     } 
     String lastValue = sources.get(sourceId).lastData.data; 
     return lastValue; 
    } 

    public void setSourceUpdateCallback(Action1<Integer> sourceUpdateFeedback) { 
     this.sourceUpdateCallback = sourceUpdateFeedback; 
    } 
} 

I możemy go używać w interfejsie tak:

import android.app.Activity; 
import android.os.Bundle; 
import android.util.Log; 
import android.widget.EditText; 
import android.widget.Toast; 

import butterknife.BindView; 
import butterknife.ButterKnife; 

public class EdiTextTestActivity extends Activity { 

    @BindView(R.id.aet_et1) 
    public EditText et1; 
    @BindView(R.id.aet_et2) 
    public EditText et2; 
    @BindView(R.id.aet_et3) 
    public EditText et3; 

    private MultiSourceCombinator multiSourceCombinator; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_edit_text_test); 
     ButterKnife.bind(this); 

     multiSourceCombinator = new MultiSourceCombinator(); 
     multiSourceCombinator.setSourceUpdateCallback(id -> { 
      Toast.makeText(EdiTextTestActivity.this, "New value from source: " + id + " : " + 
      multiSourceCombinator.getLastSourceValue(id), Toast.LENGTH_SHORT).show(); 
     }); 
    } 

    @Override 
    protected void onPause() { 
     // stop tracking all fields 
     multiSourceCombinator.stop(); 
     super.onPause(); 
    } 

    @Override 
    protected void onResume() { 
     super.onResume(); 
     // Register fields 
     multiSourceCombinator.addSource(et1, R.id.aet_et1); 
     multiSourceCombinator.addSource(et2, R.id.aet_et2); 
     multiSourceCombinator.addSource(et3, R.id.aet_et3); 
    } 
} 
+0

Korzystanie z 'PublishSubject' interesujące. Spróbuję to jak najszybciej – SachinGutte

+0

Nie miałem czasu, aby to sprawdzić wcześniej. To jest zgrabne rozwiązanie i mogę go używać na wiele różnych sposobów. Dziękuję Ci :) – SachinGutte

4

Mam rozwiązanie dla Ciebie bez użycia wyrażeń lambda (ponieważ nie mogłem skompilować go z lambdas).

Użyj tego samego operatora chciałeś:

public static <T, R> Observable<R> combineLatest(List<? extends Observable<? extends T>> sources, FuncN<? extends R> combineFunction)

Observable.combineLatest(observableList, new FuncN<Boolean>() { 
    @Override 
    public Boolean call(Object... objects) { 
     boolean isValid = true; 
     CharSequence input; 
     for (int i = 0; i < objects.length; i++) { 
      input = (CharSequence) objects[i]; 
      switch (i) { 
       case 1: 
        //First text field value 
        break; 
       case 2: 
        //Second text field value 
        break; 
       default: 
        isValid = false; 
      } 
     } 
     return isValid; 
    } 
}) 

Powodem wyrażeń lambda nie działa to prawdopodobnie w second parameter funkcji combineLatest(...):

public interface FuncN<R> extends Function { 
    R call(Object... args); 
} 

Według this post implementowanie Arbitrary Number of Arguments jest trudne i konieczne jest utworzenie obejść. RxJava v2 jest zgodny z Java 8 i ma inną realizację combineLatest

+0

Spróbuję tego :) Dziękuję za opublikowanie – SachinGutte