I have written the following view matcher for my custom view
public static Matcher<View> withValue(final Matcher<Long> longMatcher){
return new BoundedMatcher<View, IntegerField>(IntegerField.class) {
#Override
public void describeTo(Description description) {
description.appendText("with value : ");
longMatcher.describeTo(description);
}
#Override
public void describeMismatch(Object item, Description description) {
super.describeMismatch(item, description);
description.appendText("value=" + ((IntegerField)item).getValue());
}
#Override
protected boolean matchesSafely(IntegerField field) {
return longMatcher.matches(field.getValue());
}
};
when the match fails, the log doesn't contain the mismatch description I appended in the descibeMismatch() function. Is there anything that I missed?
I had the same problem. Until the feature request is implemented, you can use a custom ViewAssertion that includes the mismatch reason:
public class EspressoUtils {
// this class is copied from Espresso's source code
// (we need to copy it so that we can replace the `assertThat` function it depends on
private final static class MatchesViewAssertion implements ViewAssertion {
final Matcher<? super View> viewMatcher;
private MatchesViewAssertion(final Matcher<? super View> viewMatcher) {
this.viewMatcher = viewMatcher;
}
public void check(View view, NoMatchingViewException noViewException) {
StringDescription description = new StringDescription();
description.appendText("'");
viewMatcher.describeTo(description);
if (noViewException != null) {
description.appendText(
String.format(
"' check could not be performed because view '%s' was not found.\n",
noViewException.getViewMatcherDescription()));
throw noViewException;
} else {
description.appendText("' doesn't match the selected view.");
assertThat(description.toString(), view, viewMatcher);
}
}
/**
* A replacement for ViewMatchers.assertThat that includes the mismatch description (adapted from the source of ViewMatchers.assertThat
*/
private static <T> void assertThat(String message, T actual, Matcher<T> matcher) {
if (!matcher.matches(actual)) {
final StringDescription mismatch = new StringDescription();
matcher.describeMismatch(actual, mismatch);
Description description = new StringDescription();
description.appendText(message)
.appendText("\nExpected: ")
.appendDescriptionOf(matcher);
if(!mismatch.toString().trim().isEmpty()) {
description.appendText("\n But: ").appendText(mismatch.toString());
}
description.appendText("\n Got: ");
if (actual instanceof View) {
description.appendValue(HumanReadables.describe((View) actual));
} else {
description.appendValue(actual);
}
description.appendText("\n");
throw new AssertionFailedError(description.toString());
}
}
}
public static ViewAssertion matches(final Matcher<View> matcher) {
return new MatchesViewAssertion(matcher);
}
}
Use it like this:
onView(...).check(EspressoUtils.matches(...))
Related
I recently got following example where we are passing the action name to the method as string and then the method decides the function that needs to be called.
is this a good way of solving problem or is there some better way as well
public static final String ACTION_CHARGING_REMINDER = "charging-reminder";
public static void executeTask(Context context, String action) {
if (ACTION_INCREMENT_WATER_COUNT.equals(action)) {
incrementWaterCount(context);
} else if (ACTION_DISMISS_NOTIFICATION.equals(action)) {
NotificationUtils.clearAllNotifications(context);
} else if(ACTION_CHARGING_REMINDER.equals(action)){
issueChargeReminder(context);
}
}
I'd do something like this. This can be extended as much as you want, and obviously just an example:
static abstract class ActionHandler {
private String action;
public ActionHandler(String action) {
this.action = action;
}
public boolean canHandleAction(String input) {
return this.action.equals(input);
}
public abstract void handleAction();
}
static class OneActionHandler extends ActionHandler {
public OneActionHandler(String action) {
super(action);
}
#Override
public void handleAction() {
//...
}
}
static class TwoActionHandler extends ActionHandler {
public TwoActionHandler(String action) {
super(action);
}
#Override
public void handleAction() {
//...
}
}
static class Test {
private ActionHandler[] handlers;
public Test() {
handlers = new ActionHandler[]{new OneActionHandler("action1"), new TwoActionHandler("action2")};
}
public void handleAction(String action) {
for(ActionHandler i : handlers) {
if(i.canHandleAction(action)) {
i.handleAction();
break;
}
}
}
}
This sounds a lot like the react/redux, action/reduction pattern.
Reducers specify how the application's state changes in response to
actions sent to the store. Remember that actions only describe what
happened, but don't describe how the application's state changes.
I have a form with 4 possible options that need to be checked (could be less as well depending on circumstances). There are 2 editexts, one for email and one for a reference field when creating an order.
The email and reference fields may or may not be left empty based on conditions (which are available when the form is created). Additionally we may need to show an alert dialog to tell the user that it may not be possible to show the reference value (to the recipient of the order) and they may also need to agree to a terms and condition alert dialog.
Currently the onConfirm checks is something like this,
void onCreateOrderConfirmed(#Nullable final String receiverEmail,
#Nullable final String reference,
#Nullable final Boolean noRefAgreed,
#Nullable final Boolean termsAndConditionsAgreed) {
if (!reviewCompletionState.emailRequirementSatisfied()) {
if (!isValidEmail(receiverEmail)) {
view.showEmailError();
return;
}
reviewCompletionState = reviewCompletionState.newBuilder()
.receiverEmail(receiverEmail)
.emailRequirementSatisfied(true)
.build();
}
if (!reviewCompletionState.referenceRequirementSatisfied()) {
if (isEmpty(reference)) {
view.showReferenceError();
return;
}
reviewCompletionState = reviewCompletionState.newBuilder()
.reference(reference)
.referenceRequirementSatisfied(true)
.build();
}
if (!reviewCompletionState.noRefAgreed()) {
if (noRefAgreed == null || !noRefAgreed) {
view.showNoReferenceAlert();
return;
}
reviewCompletionState = reviewCompletionState.newBuilder()
.noRefAgreed(true)
.build();
}
if (!reviewCompletionState.termsAndConditionsAgreed()) {
if (termsAndConditionsAgreed == null || !termsAndConditionsAgreed) {
view.showTermsDisclaimerAlert();
return;
}
reviewCompletionState = reviewCompletionState.newBuilder()
.termsAndConditionsAgreed(true)
.build();
}
createOrder();
}
I would love to know if there is a way to make this validation simpler with RxJava2? (but don't currently know enough to be able to do this)
TIA
This can be a simple. There will be a lot of code, I'll show the result first.
private ReviewValidator reviewValidator = new ReviewValidator();
void onCreateOrderConfirmed(#Nullable final String receiverEmail,
#Nullable final String reference,
#Nullable final Boolean noRefAgreed,
#Nullable final Boolean termsAndConditionsAgreed) {
ReviewState reviewState = new ReviewState(receiverEmail,
reference,
noRefAgreed,
termsAndConditionsAgreed);//another model for simplicity
reviewValidator.validate(reviewState)
.flatMap(reviewState -> /* create order */)
.subscribe(this::onOrderCreated, this::onOrderCreatingError);
}
void onOrderCreated(Object order) {//or what you need here
//handle positive result
}
void onOrderCreatingError(Throwable throwable) {
if (throwable instanceof ValidateException) {
List<ValidateError> errors = ((ValidateException) throwable).getValidateErrors();
for (ValidateError error: errors) {
switch (error.getField()) {
case EMAIL: {
view.showEmailError();
return;//or break if you want show all errors
}
case REFERENCE: {
view.showReferenceError();
return;
}
//handle another errors....
}
}
//handle another error cases...
}
First, create model for reviewState:
public class ReviewState {
private String receiverEmail;
private String reference;
private Boolean noRefAgreed;
private Boolean termsAndConditionsAgree;
public ReviewState(String receiverEmail,
String reference,
Boolean noRefAgreed,
Boolean termsAndConditionsAgree) {
this.receiverEmail = receiverEmail;
this.reference = reference;
this.noRefAgreed = noRefAgreed;
this.termsAndConditionsAgree = termsAndConditionsAgree;
}
public String getReceiverEmail() {
return receiverEmail;
}
public String getReference() {
return reference;
}
public Boolean getNoRefAgreed() {
return noRefAgreed;
}
public Boolean getTermsAndConditionsAgree() {
return termsAndConditionsAgree;
}
}
Then create you own validator. It is not necessary to create a whole model, you can create validator for every field and and link them with flatMap(), your choice.
public class ReviewValidator extends Validator<ReviewState> {
#Override
protected List<ValidateFunction> getValidateFunctions(ReviewState reviewState) {
List<ValidateFunction> validateFunctions = new LinkedList<>();
validateFunctions.add(() -> validateEmail(reviewState.getReceiverEmail()));
validateFunctions.add(() -> validateReference(reviewState.getReference()));
//another validation methods
return validateFunctions;
}
private ValidateError validateEmail(String email) {
if (TextUtils.isEmpty(email)) {
return new ValidateError(Field.EMAIL);//Field.EMAIL - just enum
}
return null;
}
private ValidateError validateReference(String reference) {
if (TextUtils.isEmpty(reference)) {
return new ValidateError(Field.REFERENCE);
}
return null;
}
//....
//another validation methods
}
Abstract class for validator:
public abstract class Validator<Model> {
public Single<Model> validate(Model model) {
return Single.just(model)
.map(this::validateModel)
.flatMap(this::processResult);
}
private Single<Model> processResult(ValidateResultModel<Model> validateResultModel) {
return Single.create(subscriber -> {
List<ValidateError> validateErrors = validateResultModel.getValidateErrors();
if (validateErrors.isEmpty()) {
subscriber.onSuccess(validateResultModel.getModel());
} else {
subscriber.onError(new ValidateException(validateErrors));
}
});
}
private ValidateResultModel<Model> validateModel(Model model) {
List<ValidateError> errors = new LinkedList<>();
for (ValidateFunction validateFunctions : getValidateFunctions(model)) {
ValidateError error = validateFunctions.validate();
if (error != null) {
errors.add(error);
}
}
return new ValidateResultModel<>(model, errors);
}
protected abstract List<ValidateFunction> getValidateFunctions(Model model);
protected interface ValidateFunction {
#Nullable
ValidateError validate();
}
}
Helper classes for validator...
public class ValidateError {
private Field field;
public ValidateError(Field field) {
this.field = field;
}
public Field getField() {
return field;
}
}
class ValidateResultModel<T> {
private T model;
private List<ValidateError> validateErrors;
ValidateResultModel(T model, List<ValidateError> validateErrors) {
this.model = model;
this.validateErrors = validateErrors;
}
T getModel() {
return model;
}
List<ValidateError> getValidateErrors() {
return validateErrors;
}
}
public class ValidateException extends RuntimeException {
private List<ValidateError> validateErrors;
ValidateException(List<ValidateError> validateErrors) {
this.validateErrors = validateErrors;
}
public List<ValidateError> getValidateErrors() {
return validateErrors;
}
}
Initially, I took the idea from here: https://github.com/matzuk/TestableCodeMobius/tree/master/app/src/main/java/com/matsyuk/testablecodemobius/business/transfer/validation
i think you should the RxJava CombineLatest, so you need all of the form input are producing an observable, then you just combine it and adjust the view
as a reference you can check:
https://medium.com/#etiennelawlor/rxjava-on-the-sign-in-screen-9ecb66b88572
Using RxJava for email login validation, an observable is emitting twice
========
example:
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;
import com.jakewharton.rxbinding2.view.RxView;
import com.jakewharton.rxbinding2.widget.RxCompoundButton;
import com.jakewharton.rxbinding2.widget.RxTextView;
import io.reactivex.Observable;
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EditText receiverText = findViewById(R.id.input_receiver);
EditText referenceText = findViewById(R.id.input_reference);
CheckBox checkRef = findViewById(R.id.check_ref);
CheckBox checkTerms = findViewById(R.id.check_terms);
Button buttonLogin = findViewById(R.id.button_login);
Observable<CharSequence> receiverObservable = RxTextView.textChanges(receiverText).skip(1); // can add more logic
Observable<CharSequence> referenceObservable = RxTextView.textChanges(referenceText).skip(1); // can add more logic
Observable<Boolean> refCheckObservable = RxCompoundButton.checkedChanges(checkRef); // can add more logic
Observable<Boolean> termsCheckObservable = RxCompoundButton.checkedChanges(checkTerms); // can add more logic
Observable<String> combineObservable = Observable.combineLatest(
receiverObservable,
referenceObservable,
refCheckObservable,
termsCheckObservable, (receiverCharSequence, referenceCharSequence, refBoolean, termsBoolean) -> {
// add logic here for now it is only combine the input
return receiverCharSequence + " " + referenceCharSequence + " " + refBoolean + " " + termsBoolean ;}
);
RxView.clicks(buttonLogin).flatMap(o -> { return combineObservable;}).distinctUntilChanged().subscribe(string -> {
Toast.makeText(this, string, Toast.LENGTH_LONG).show();
});
}
}
I have the following code:
/**
* Request wrapped around flowable.
*/
public abstract class RequestFlowable<T> {
private final PublishProcessor<String> mPublish;
private String mName;
public RequestFlowable(String name) {
mName = name;
mPublish = PublishProcessor.create();
}
public Flowable<T> getFlowable() {
//return createAction();
return mPublish.compose(new FlowableTransformer<String, T>() {
#Override
public Publisher<T> apply(#NonNull Flowable<String> upstream) {
return createAction();
}
});
/*
return mPublish.flatMap(new Function<String, Publisher<? extends T>>() {
#Override
public Publisher<? extends T> apply(#NonNull String s) throws Exception {
return createAction();
}
});
*/
}
protected abstract Flowable<T> createAction();
public String getName() {
return mName;
}
public void start() {
mPublish.onNext("processCommand");
}
#Override
public String toString() {
return "Request: " + mName;
}
}
Now for Single
#EDIT 2
public abstract class Request<T> {
private final SingleSubject<Object> mPublish;
private String mName;
public Request(String name) {
mName = name;
mPublish = SingleSubject.create();
}
public Single<T> getSingle() {
return mPublish.flatMap(o -> createAction());
}
protected abstract Single<? extends T> createAction();
public String getName() {
return mName;
}
public void start() {
mPublish.onSuccess("Start");
}
#Override
public String toString() {
return "Request: " + mName;
}
}
The code from the above works when used with compose, like in code from above but, if instead I put the commented code - aka flatMap for some reason createAction is not executed.
EDIT 2
The code from the above is called from another class. The corresponding code is attached below(important parts of class added):
public class RequestQueue implements RequestController {
private static final String TAG = RequestQueue.class.getSimpleName();
private PublishSubject<Request> mRequest;
private PublishSubject<RequestFlowable> mRequestFlowable;
#Override
public <T> Single<T> registerRequest(Request<T> request) {
mRequest.onNext(request);
return request.getSingle();
}
#Override
public <T> Flowable<T> registerRequestFlowable(RequestFlowable<T> request) {
mRequestFlowable.onNext(request);
return request.getFlowable();
}
public RequestQueue() {
mRequest = PublishSubject.create();
mRequestFlowable = PublishSubject.create();
mRequest.subscribe(this::actionOnRequest);
mRequestFlowable.subscribe(this::actionOnRequest);
}
private void actionOnRequest(Request request) {
Log.d(TAG, "actionOnRequest() called with: request = [" + request + "]");
request.start();
}
private void actionOnRequest(RequestFlowable request) {
Log.d(TAG, "actionOnRequest() called with: request = [" + request + "]");
request.start();
}
}
(From my comments:)
Why does Single work?
SingleSubject retains the single terminal event it received. Since it can only receive onSuccess and onError, it will "replay" that to late subscribers (also this is why there is no separater ReplaySingleSubject). When you call onSuccess on the SingleSubject, that value is remembered and promplty reemitted when the later subscription happens, calling your createAction. PublishProcessor also remembers its terminal events but onNext is not a terminal event, hence dropped without consumer.
How can the desired behavior be achieved via Processor?
You could reorganize your logic, use BehaviorProcessor or ReplayProcessor.createWithSize(1). Calling onComplete won't execute the flatMap function either.
I have a listener:
public interface OnCompleteListener<T> {
void onComplete(T data);
}
I store it in list:
private List<OnCompleteListener<?>> mListeners = new ArrayList<>();
// ...
public void addType1Listener() {
addListener(new OnCompleteListener<Type1>() {
//...
});
}
public void addType2Listener() {
addListener(new OnCompleteListener() {
//...
});
}
private <T> void addListener(OnCompleteListener<T> listener) {
mListeners.add(listener);
}
I am trying to get it by this way:
public <T> OnRequestsCompleteListener<T> get(int i) {
return (OnRequestsCompleteListener<T>) mListeners.get(i);
}
Type1 and Type2 have no parent class and cannot have.
But I get 'unchecked cast' warning. How to get it correctly?
Instead of specifying genetic T in a method, you need a class with generic T
public class ListenerCollection <T> {
private List<OnCompleteListener<T>> mListeners = new ArrayList<OnCompleteListener<T>>();
public void addListener(OnCompleteListener<T> listener) {
mListeners.add(listener);
}
public OnCompleteListener<T> get(int i) {
return mListeners.get(i);
}
}
Suppose you have a class OnRequestCompleteListener, implemening OnCompleteListener<String>. Then you do:
ListenerCollection<String> lcollection;
...........
OnRequestCompleteListener newListener = (OnRequestCompleteListener) lcollection.get(i);
That doesn't give any warning.
I have my Class Adapter , and I need to have the access of two class ! Then I can put public class JSONAdapter extends ArrayAdapter<Voiture> { for have access in my class " Voiture " but I need to have the acceesss in my class "Moniteur" too , and I can"t put that :
public class JSONAdapter extends ArrayAdapter<Voiture>,ArrayAdapter<Moniteur> {
I need to view attributes of my class " Voiture " and " Moniteur " ...
Do you have the solution for me please ? Thanks
EDIT : Ok thanks you , this is the code of my class VOITURE :
public class Voiture {
private int idV = -1; // permet de voir si le parent est enregistré dans la BDD
private String marqueV;
private String dateAchatV;
private String PlaqueImmatriculationV;
public Voiture(String marqueV, String plaqueImmatriculationV) {
this.marqueV = marqueV;
PlaqueImmatriculationV = plaqueImmatriculationV;
}
public Voiture(JSONAdapter jsonAdapter) {
this.marqueV = marqueV;
this.PlaqueImmatriculationV = PlaqueImmatriculationV;
}
public int getIdV() {
return idV;
}
public void setIdV(int idV) {
this.idV = idV;
}
public String getMarqueV() {
return marqueV;
}
public void setMarqueV(String marqueV) {
this.marqueV = marqueV;
}
public String getDateAchatV() {
return dateAchatV;
}
public void setDateAchatV(String dateAchatV) {
this.dateAchatV = dateAchatV;
}
public String getPlaqueImmatriculationV() {
return PlaqueImmatriculationV;
}
public void setPlaqueImmatriculationV(String plaqueImmatriculationV) {
PlaqueImmatriculationV = plaqueImmatriculationV;
}
}
This is the code of my class Moniteur :
public class Moniteur {
private int idM;
private String nomM;
private String prenomM;
private String adresseM;
private String telephoneM;
public Moniteur(String nomM, String prenomM,String adresseM,String telephoneM) {
this.nomM = nomM;
this.prenomM = prenomM;
this.adresseM = adresseM;
this.telephoneM = telephoneM;
}
public int getIdM() { return idM; }
public void setIdM(int idM) { this.idM = idM; }
public String getNomM() {
return nomM;
}
public void setNomM(String nomM) {
this.nomM = nomM;
}
public String getPrenomM() {
return prenomM;
}
public void setPrenomM(String prenomM) {
this.prenomM = prenomM;
}
public String getAdresseM() {
return adresseM;
}
public void setAdresseM(String adresseM) {
this.adresseM = adresseM;
}
public String getTelephoneM() {
return telephoneM;
}
public void setTelephoneM(String telephoneM) {
this.telephoneM = telephoneM;
}
}
The purpose of these two classes and how they relate is not clear, but if you want to store both of them in a single container (which is what you are trying to do) you will need to define a common interface between them (or an abstract class). I don't speak French so you will have a easier time creating a good name.
I suggest creating an interface:
public interface AbstractItem
{
//TODO define common functions in this class
}
then implement this interface in your classes:
public class Voiture implements AbstractItem
{ ... }
public class Moniteur implements AbstractItem
{ ... }
Then, you can create an array adapter that will hold both of these items:
public class JSONAdapter extends ArrayAdapter<AbstractItem>
{ ... }
If I understand correctly, "Voiture" means "car" and "Moniteur" means "instructor" or "teacher".
So, it sounds like you are implementing a type of "driving school" where there are teachers with cars that they use/drive.
If that is the case, you really only need an ArrayAdapter<Monituer> and you could implement your Moniteur class like so.
public class Moniteur {
private List<Voiture> voitures;
public Monituer() {
voitures = new ArrayList<Voiture>();
}
public void ajouterVoiture(Voiture v) {
voitures.add(v);
}
public List<Voiture> obtientVoitures() {
return voitures;
}
}
Or maybe I don't understand what is trying to be displayed in the adapters. In that case, feel free to comment below.