In the context of a android application I want to create an ReplaySubject from an array that I retrieve from the saved bundle.
So far I have:
subject = ReplaySubject.create();
for (SomeDto dto : dtos) {
subject.onNext(dto);
}
I was hoping for a more straight forward way that will avoid the iteration.
Do you mean ReplaySubject?
If the DTO list you are retrieving from the bundle is constant and the subscribers don't need to be notified of changes after being retrieved from the bundle you can just use a standard Observable no need to use a Subject.
List<SomeDto> dtos; // Get list of SomeDto from bundle.
Observable<SomeDto> dtoObservable = Observable.from(dtos);
Each time a subscription to dtoObserbale is made it will replay all the items.
If your subscribers need to notified of changes to the DTO list in addition to the initial values then the way you are doing it is just fine.
EDIT: Another idea you might like. Create an observable from the DTOs retrieved from the Bundle. Then concatenate it with the DTO subject.
List<SomeDto> dtos; // Get list of SomeDto from bundle.
ReplaySubject<SomeDto> subject = ReplaySubject.create();
Observable<SomeDto> dtoObservable =
Observable
.from(dtos)
.concatWith(subject);
// At some point later.
subject.onNext(anotherDto);
Each subscriber will see all values. Here's an example with strings:
final List<String> strings = Arrays.asList("1", "2");
final ReplaySubject<String> stringsSubject = ReplaySubject.create();
final Observable<String> stringObservable =
Observable
.from(strings)
.concatWith(stringsSubject);
stringObservable
.subscribe(new Action1<String>() {
#Override
public void call(String s) {
logger.v("stringObservable1 - " + s);
}
});
stringsSubject.onNext("3");
stringObservable
.subscribe(new Action1<String>() {
#Override
public void call(String s) {
logger.v("stringObservable2 - " + s);
}
});
stringsSubject.onNext("4");
stringObservable
.subscribe(new Action1<String>() {
#Override
public void call(String s) {
logger.v("stringObservable3 - " + s);
}
});
stringsSubject.onNext("5");
Outputs the following:
stringObservable1 - 1
stringObservable1 - 2
stringObservable1 - 3
stringObservable2 - 1
stringObservable2 - 2
stringObservable2 - 3
stringObservable1 - 4
stringObservable2 - 4
stringObservable3 - 1
stringObservable3 - 2
stringObservable3 - 3
stringObservable3 - 4
stringObservable1 - 5
stringObservable2 - 5
stringObservable3 - 5
Related
I am trying to see if I can spawn 1 million Observables on io() and computation() Schedulers.
public static void observableLimit() {
sum = 0;
long lowerBound = 0;
long higherBound = 1000;
Flowable.fromCallable(() -> {
Flowable.rangeLong(lowerBound, higherBound + 1)
.subscribe(integer -> Observable.just(integer)
.subscribeOn(Schedulers.io())
.subscribe(j -> {
printNum(j);
sum = sum + j;
}));
return true;
}).blockingSubscribe(aBoolean -> {
long actualSum = (higherBound * (higherBound + 1)) / 2;
System.out.println("");
System.out.println("SUM: " + sum);
Assert.assertEquals(actualSum, sum);
});
}
For higherBound = 100 it works most of the time, for 1000 it works sometimes and fails most of the time and for 10000 it almost fails everytime, it works if I tell it to run it on newThread() and if I don't use subscribeOn() at all.
How can I fix this behaviour?
The problem you're facing is not about of some limitations of Observables, but a problem with your code. You're blockingSubscribe to a Flowable that have no relation with the Flowable that span all other threads. for small values of higherBound you'll see that the code works while for large values doesn't and that because the outer Flowable may be as fast as the inner Flowable for small higherBound but collapse faster for high values of higherBound.
What I'm trying to say is that in order to see the right result you need to syncronize with the Flowable that span all the other threads instead of the outer one. I also would replace long sum by a thread-safe implementation LongAdder sum, you can achieve this using flatMap operator.
Flowable.rangeLong(lowerBound, higherBound + 1)
.flatMap(t -> Flowable.just(t)
.subscribeOn(Schedulers.io())
)
.doOnNext(sum::add)
.doOnComplete(() -> {
long actualSum = (higherBound * (higherBound + 1)) / 2;
log("SUM: " + sum.longValue() + ", ACTUAL: " + actualSum);
log("Equals: " + (actualSum == sum.longValue()));
})
.blockingSubscribe();
How can I fix this behaviour?
Don't use that pattern. Why do you want to do that in the first place?
io and newThread create OS threads and are fundamentally limited by your OS' capabilities and available memory.
computation has a fixed set of threads and can handle much larger number of Flowables because they get assigned to one of the existing worker threads.
I am using Room (1.0.0.rc1) with RX, my Dao is defined is this way:
#Dao
interface AccountDao {
#Query("SELECT * FROM Account ORDER BY name")
fun all(): Flowable<List<Account>>
}
I am subscribing this way:
dao
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { accounts = it }
I have more than one place in the code who subscribes to the flowable. The first to subscribe gets the data, the other ones don't.
How can I make an observable that will emit the actual content every time someones subscribes and will also notify every subscriber when the data changes?
You can use replay to emit lastest value every time someone subscribes. And use distinctUntilChanged to notify only when data changes.
Here is the sample:
import io.reactivex.Observable;
import io.reactivex.subjects.BehaviorSubject;
public class Q47000608 {
public static void main(String[] args) {
BehaviorSubject<Integer> bs = BehaviorSubject.createDefault(1);
Observable<Integer> o = bs.replay(1).autoConnect().distinctUntilChanged();
o.subscribe(i -> System.out.println("s1 accept " + i));
bs.onNext(2);
o.subscribe(i -> System.out.println("s2 accept " + i));
o.subscribe(i -> System.out.println("s3 accept " + i));
bs.onNext(3);
o.subscribe(i -> System.out.println("s4 accept " + i));
bs.onNext(4);
}
}
And output:
s1 accept 1
s1 accept 2
s2 accept 2
s3 accept 2
s1 accept 3
s2 accept 3
s3 accept 3
s4 accept 3
s1 accept 4
s2 accept 4
s3 accept 4
s4 accept 4
I want to map/convert an object to another object in background thread and have it on main thread as soon as a single conversation is completed.
Observable.just(1,2,3,4,5)
.map(new Func1<Integer, String>() {
#Override
public String call(Integer integer) {
Log.d(TAG, "mapping number " + integer);
return String.valueOf(integer) + " mapped on: " + Thread.currentThread().getName();
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<String>() {
#Override
public void onCompleted() {
Log.d(TAG, "onCompleted on: " + Thread.currentThread().getName());
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(String integer) {
Log.d(TAG, integer + " received on: "+ Thread.currentThread().getName());
}
});
The Result is:
D: mapping number 1
D: mapping number 2
D: mapping number 3
D: mapping number 4
D: mapping number 5
D: 1 mapped on: RxNewThreadScheduler-1 received on: main
D: 2 mapped on: RxNewThreadScheduler-1 received on: main
D: 3 mapped on: RxNewThreadScheduler-1 received on: main
D: 4 mapped on: RxNewThreadScheduler-1 received on: main
D: 5 mapped on: RxNewThreadScheduler-1 received on: main
D: onCompleted on: main
However the conversion may took a while and I expect to receive them as soon as the conversion is done.
D: mapping number 1
D: 1 mapped on: RxNewThreadScheduler-1 received on: main
D: mapping number 2
D: 2 mapped on: RxNewThreadScheduler-1 received on: main
D: mapping number 3
D: 3 mapped on: RxNewThreadScheduler-1 received on: main
D: mapping number 4
D: 4 mapped on: RxNewThreadScheduler-1 received on: main
D: mapping number 5
D: 5 mapped on: RxNewThreadScheduler-1 received on: main
D: onCompleted on: main
There is no need to set the global buffer size, just use the observeOn(Scheduler, int) overload where you can specify the prefetch value to be 1. That will only ask for the next value if the previous value has been processed.
This is due to RxJava applying backpressure on the operators in the chain you are using above. The downstream operators such as ObserveOn request data from upstream by chunks, and not by individual items for efficiency. If you set the buffer size to one, this will effectively achieve of what you would expect with cost of efficiency:
-Drx.ring-buffer.size=1
Specifically that would be quite awful for upstreams that have expensive roundtrip calls.
EDIT:
You can use zip with BehaviorSubject to sort of serialize your down and up stream emissions:
BehaviorSubject<Void> signal = BehaviorSubject.create();
signal.onNext(null); // <- pair up the signal with the first item immediately
Observable.just(1,2,3,4,5)
.zipWith(signal, (item,v)->item) //only emit a next item when there is a "receipt acknowledgement" from the down stream
.observeOn(Schedulers.newThread()) //<- needed to avoid fetching subsequent items in UI thread
.map(new Func1<Integer, String>() {
#Override
public String call(Integer integer) {
Log.d(TAG, "mapping number " + integer);
return String.valueOf(integer) + " mapped on: " + Thread.currentThread().getName();
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<String>() {
#Override
public void onCompleted() {
Log.d(TAG, "onCompleted on: " + Thread.currentThread().getName());
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(String integer) {
Log.d(TAG, integer + " received on: "+ Thread.currentThread().getName());
signal.onNext(null); //<- acknowledge receipt - allow emitting next item from upstream
}
});
I need to check if it exits in DB load and pass to view
if not
load from Server ,Store in DB ,then pass to view
I confused in methods doOnNext(), flatMap() ,I don't which one to use
I couldn't understand the meaning of methods of doOnNext(), flatMap()
I will try to help you with this operators.
As documentation states about doOnNext()
modifies the source Observable so that it invokes an action when it
calls
So check this sample out:
public class ExampleUnitTest {
#Test
public void testSample() throws Exception {
Observable.just(1,2,3,4,5)
.doOnNext(number -> System.out.println("doOnNext: " + number))
.subscribe(number -> System.out.println("onNext: " + number));
}
}
It will print:
doOnNext: 1
onNext: 1
doOnNext: 2
onNext: 2
doOnNext: 3
onNext: 3
doOnNext: 4
onNext: 4
doOnNext: 5
onNext: 5
What about flatMaps? Let's check documentation again:
Returns an Observable that emits items based on applying a function
that you supply to each item emitted by the source Observable, where
that function returns an Observable, and then merging those resulting
Observables and emitting the results of this merger.
So, flatMap creates a new stream based on each item emitted by original stream and them merge those new streams into a single one.
Let's see some code again:
public class ExampleUnitTest {
#Test
public void testSample() throws Exception {
Observable.just(1, 2, 3, 4, 5)
.flatMap(number -> {
final String first = number + "A";
final String second = number + "B";
return Observable.just(first, second);
})
.subscribe(flattedItem -> System.out.println("onNext: " + flattedItem));
}
}
It will print:
onNext: 1A
onNext: 1B
onNext: 2A
onNext: 2B
onNext: 3A
onNext: 3B
onNext: 4A
onNext: 4B
onNext: 5A
onNext: 5B
Hope that it helps you bro!
Best regards.
I have a list view with place distances, as shown below:
100
200
300
400
500
50
600
70
40
i need to sort this place distances in both ascending and descending order. So wat i did was, sorting the adapter and use adapter.notifyDataSetChanged(). Everything works fine if the distances are 3 digit numbers ie., 230, 450, 352, 254 and so on. But if the place distance is 2 digit like 52, 65, 75 etc, i am getting wrong sorted values as shown below.
for ascending order : after sort i am getting this order
100
200
300
400
500
600
50
60
70
for descending order : after sort i am getting this order
70
60
50
600
500
400
300
200
100
my code for sorting is shown below:
case R.id.action_sort_dist_fn:
adapter.sort(new Comparator<Place>() {
#Override
public int compare(Place lhs, Place rhs) {
return rhs.getPlaceDistance().compareTo(
lhs.getPlaceDistance());
};
});
adapter.notifyDataSetChanged();
break;
case R.id.action_sort_dist_nf:
adapter.sort(new Comparator<Place>() {
#Override
public int compare(Place lhs, Place rhs) {
return lhs.getPlaceDistance().compareTo(
rhs.getPlaceDistance());
};
});
Place.java
public class Place {
String placeName = "";
String placeDistance="";
String placeCategoryIcon;
public Place(String placeName, String placeDistance,
String placeCategoryIcon) {
this.placeName = placeName;
this.placeDistance = placeDistance;
this.placeCategoryIcon = placeCategoryIcon;
}
public String getPlaceName() {
return placeName;
}
public void setPlaceName(String placeName) {
this.placeName = placeName;
}
public String getPlaceDistance() {
return placeDistance;
}
public void setPlaceDistance(String placeDistance) {
this.placeDistance = placeDistance;
}
public String getPlaceCategoryIcon() {
return placeCategoryIcon;
}
public void setPlaceCategoryIcon(String placeCategoryIcon) {
this.placeCategoryIcon = placeCategoryIcon;
}
}
First of all the sorting you give is not the actual one you are getting. E.g the second one is actually:
70
600
60
500
50
400
300
200
100
The comparison shouldn't be done using strings - it compares lexicographically, which means that the comparison is as described here. So "abc" is greater than "ab" and "d" is greater than "ccc".
You want to compare the number values, so please do so. One option is to use Integer.valueOf to get the actual integer value equivalents of the values you use.
By the way I see no gain for you to have placeDistance a String field. Why don't you have it as Integer all along?
You're sorting Strings. The natural ordering for String is the lexicographical ordering. What you want is integer ordering. So use an int rather than a String for Place.placeDistance. Or at least transform the strings to compare to integers, and compare those integers.
Also, calling your field placeDistance adds unnecessary noise. Just call it distance (and do the same for the other fields, of course).
The problem is that in your comparator you use:
return rhs.getPlaceDistance().compareTo(lhs.getPlaceDistance());
and placeDistance is a string, therefore you are comparing Strings, not numeric values.
I think it is because you are comparing strings. try changing your compare override to:
#Override
public int compare(Place lhs, Place rhs)
{
return Integer.parseInt(rhs.getPlaceDistance()).compareTo(
Integer.parseInt(lhs.getPlaceDistance()));
};
I think the ideal solution might be to hold integers in your place rather than Strings.
Note that if you will have decimal places you should try Float or Double instead of Integer