This is best explained with a short example.
let's say this is my source observable that I want to filter
Observable.interval(1, TimeUnit.SECONDS)
I use a checkbox to handle the filter state. When the box is not checked, I want to skip all values.
I use RxAndroid to get an observable for the checkbox like this:
RxCompoundButton.checkedChanges(checkBox)
here is my code:
Observable.combineLatest(
RxCompoundButton.checkedChanges(checkBox)
, Observable.interval(1, TimeUnit.SECONDS)
, (isChecked, intervalCounter) -> {
if (!isChecked) {
return null;
} else {
return intervalCounter;
}
}
).filter(Objects::nonNull)
and I get the desired output
interval 1--2--3--4--5--6--7--8--9
checkbox 0-----1--------0-----1---
combineLatest
result ------3--4--5--------8--9
I am just getting started with RxJava and my "solution" does not feel right, because:
You can see that the combine function returns a magic value null
and then the filter will skip these nulls
.
That means that the filter function is called even though I already know, that I want to skip this data.
- maybe I should use another operator
- is there a way to use the checkbox-observable in a filter function
- maybe there is some way in the combine function to signal that I want to skip this data
Your pictures of the desired output is not correct. In your implementation combineLatest
is combining a null
into the stream :
interval 1--2--3--4--5--6--7--8--9
checkbox 0-----1--------0-----1---
combineLatest N--N--3--4--5--N--N--8--9
filter(NonNull)------3--4--5--------8--9
IMHO, using null
as a signal is not good in Rx Stream, developers can easily fall into NullPointerException
.
To avoid the use of null
, there are two measures. The first one is to transform the result to a Pair, and apply filter & map later.
A very simple Pair
Class:
public class Pair<T, U> {
T first;
U second;
public Pair(T first, U second) {
this.first = first;
this.second = second;
}
}
Then the whole implementation would like this:
Observable.combineLatest(
RxCompoundButton.checkedChanges(checkBox)
, Observable.interval(1, TimeUnit.SECONDS)
, (isChecked, intervalCounter) -> new Pair<>(isChecked,intervalCounter)
).filter(pair -> pair.first)
.map(pair -> pair.second) // optional, do this if you only need the second part
The data flow:
interval 1 2 3 4
| | | |
checkbox F | T | F | T F
| | | | | | |
combineLatest F1 T1 T2 F2 F3 T3 F4
| | |
filter(first=T) T1 T2 T3
| | |
map(second) 1 2 3
Or if you can Java 8 in your project, use Optional
to avoid null
, which is very similar to your solution but it gives the awareness for others developers that the Stream signal is Optional
.
Observable<Optional<Integer>> o1 = Observable.combineLatest(
RxCompoundButton.checkedChanges(checkBox)
, Observable.interval(1, TimeUnit.SECONDS)
, (isChecked, intervalCounter) -> {
if (!isChecked) {
return Optional.empty();
} else {
return Optional.of(intervalCounter);
}
}
)
Observable<Integer> o2 = o1.filter(Optional::isPresent).map(Optional::get)
Some easier approach which is not same with combineLatest
but identical to your desired result pictures.
// Approach 1
Observable.interval(1, TimeUnit.SECONDS).filter( i -> checkBox.isEnabled())
// Approach 2
Observable.interval(1, TimeUnit.SECONDS)
.withLatestFrom(
RxCompoundButton.checkedChanges(checkBox),
(isChecked, intervalCounter) -> {
if (!isChecked) {
return Optional.empty();
} else {
return Optional.of(intervalCounter);
}
}).filter(Optional::isPresent).map(Optional::get)