Jean-Paul Manuel
Jean-Paul Manuel

Reputation: 546

RxAndroid: Observe an EditText while also observing touches on 2 Views

Let me pick your brains about a small task that's taking me days to figure out...

I have an EditText, which the user will input a String that will be used to make a Wikipedia search and feed a ListView with the results. The EditText should have a throttle of 1 seconds, and will have to be filtered in order to check with a list of 'inappropriate' Strings. The search will not go through if the input is in that list of String, Unless the user touches on 2 hidden views found on 2 corners of the screen at the same time, then it will skip this check.

Here is a few snippets that would help you understand what I already have...

// a list of inappropriate content
private static String[] INAPPROPRIATE_CONTENT = {
            "test"
        };

// subscription to the touches happening on hidden view #1
Subscription subSecretLeft = RxView.touches(vSecretLeft)
        .filter(motionEvent -> motionEvent.getAction() == MotionEvent.ACTION_DOWN || motionEvent.getAction() == MotionEvent.ACTION_UP)
        .subscribe(motionEvent -> secretLeftClicked = motionEvent.getAction() == MotionEvent.ACTION_DOWN);

// subscription to the touches happening on hidden view #2
RxView.touches(vSecretRight)
        .filter(motionEvent -> motionEvent.getAction() == MotionEvent.ACTION_DOWN || motionEvent.getAction() == MotionEvent.ACTION_UP)
        .subscribe(motionEvent -> secretRightClicked = motionEvent.getAction() == MotionEvent.ACTION_DOWN);

// subscription to the EditText
RxTextView.textChanges(etxtSearchFilter)
        .throttleLast(1, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
        .map(c -> c.toString().trim())
        .filter(s -> !(s.isEmpty() || isInappropriate(s)))
        .subscribe(s -> fetchWikiSearch(s));

Where isInappropriate(String s) is the method that checks whether the term is found in the array, and fetchWikiSearch(String s) performs the search and populates the ListView.

I tried several ways, and the last method I could come up is as follows:

Observable.zip(
        Observable.combineLatest(
                RxView.touches(vSecretLeft)
                        .filter(motionEvent -> motionEvent.getAction() == MotionEvent.ACTION_DOWN || motionEvent.getAction() == MotionEvent.ACTION_UP || motionEvent.getAction() == MotionEvent.ACTION_CANCEL),
                RxView.touches(vSecretRight)
                        .filter(motionEvent -> motionEvent.getAction() == MotionEvent.ACTION_DOWN || motionEvent.getAction() == MotionEvent.ACTION_UP || motionEvent.getAction() == MotionEvent.ACTION_CANCEL),
                (ev1, ev2) -> ev1.getAction() == MotionEvent.ACTION_DOWN && ev2.getAction() == MotionEvent.ACTION_DOWN
        ).asObservable(),
        RxTextView.textChanges(etxtSearchFilter)
                .throttleLast(1, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
                .map(c -> c.toString().trim())
                .filter(s -> !s.isEmpty()),
        (overrideFilter, s) ->
                overrideFilter || !isInappropriate(s) ? s : "BLOCKED"
).subscribe(s -> Log.i("ObservableZip", "s: " + s));

But obviously, as long as I don't touch on those views, it won't emit anything. Even the Observable.combileLatest() isn't working great, as it won't emit only when both views are getting a MotionEvent.ACTION_DOWN...

If you have any tips or the actual solution, don't hesitate to comment. Thanks.

Upvotes: 1

Views: 891

Answers (2)

JohnWowUs
JohnWowUs

Reputation: 3083

You seem to be on the right track but you might want to "initialize" your hidden views with startsWith i.e.

// subscription to the touches happening on hidden view #1
Subscription subSecretLeft = RxView.touches(vSecretLeft)
        .filter(motionEvent -> motionEvent.getAction() == MotionEvent.ACTION_DOWN || motionEvent.getAction() == MotionEvent.ACTION_UP)
        .startsWith(MotionEvent.ACTION_UP)
        .subscribe(motionEvent -> secretLeftClicked = motionEvent.getAction() == MotionEvent.ACTION_DOWN);

Upvotes: 1

Bartek Lipinski
Bartek Lipinski

Reputation: 31438

Wouldn't that be enough?

Observable.combineLatest(
        RxView.touches(vSecretLeft)
                .filter(motionEvent -> motionEvent.getAction() == MotionEvent.ACTION_DOWN || motionEvent.getAction() == MotionEvent.ACTION_UP || motionEvent.getAction() == MotionEvent.ACTION_CANCEL),
        RxView.touches(vSecretRight)
                .filter(motionEvent -> motionEvent.getAction() == MotionEvent.ACTION_DOWN || motionEvent.getAction() == MotionEvent.ACTION_UP || motionEvent.getAction() == MotionEvent.ACTION_CANCEL),
        RxTextView.textChanges(etxtSearchFilter)
                .debounce(1, TimeUnit.SECONDS)
                .map(c -> c.toString().trim())
                .filter(s -> !s.isEmpty()),
        (motionEventLeft, motionEventRight, entered) -> {
            boolean overrideFilter = motionEventLeft.getAction() == MotionEvent.ACTION_DOWN && motionEventRight.getAction() == MotionEvent.ACTION_DOWN;
            return overrideFilter || !isInappropriate(entered) ? entered : "BLOCKED";
        })
        .subscribe(s -> Log.i("ObservableCombineLatest", "s: " + s));

(I also changed throttleLast to debounce as it somehow feels better for this purpose)

EDIT

Observable.combineLatest(
        RxView.touches(vSecretLeft)
                .map(motionEvent -> motionEvent.getAction())
                .filter(action -> action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)
                .startWith(MotionEvent.ACTION_UP),
        RxView.touches(vSecretRight)
                .map(motionEvent -> motionEvent.getAction())
                .filter(action -> action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)
                .startWith(MotionEvent.ACTION_UP),
        RxTextView.textChanges(etxtSearchFilter)
                .debounce(1, TimeUnit.SECONDS)
                .map(c -> c.toString().trim())
                .filter(s -> !s.isEmpty()),
        (leftAction, rightAction, entered) -> {
            boolean overrideFilter = leftAction == MotionEvent.ACTION_DOWN && rightAction == MotionEvent.ACTION_DOWN;
            return overrideFilter || !isInappropriate(entered) ? entered : "BLOCKED";
        })
        .subscribe(s -> Log.i("ObservableCombineLatest", "s: " + s));

Upvotes: 1

Related Questions