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
Related
I have an app in which gives you a certain photo based on an integer being fetched via
class RandomImageLogic(){
fun retrive(): Int{
return (1..9).random()
}
}
However, it is not professional to have a repeated outcome, as I desire a random integer to be fetched each time I call the function in order for the image to be different each time my button is pressed. How can I fetch a new random integer whenever the button calls the function?
The easy way is to pass in the last random number you received and filter it out.
fun retrive(except: Int): Int{
return ((1..9).filter {it != except}).random();
}
In your case, this method might not be called extremely often (only when the user clicks a button).
If this method was to be called more often, filter on an IntRange should be used with care (as proposed in #avalerio's answer).
This iterates the whole range (unnecessarily costing time) and it would create a temporary ArrayList on every call (creating unnecessary garbage and triggering the garbage collector more often than needed).
Here is a sample object NonRepeatingRandom (you can also implement it as a class if you wish). retrieve (which has been expanded with a max parameter and basic sanity checks) recursivly calls itself again if the same number would be generated twice in a row:
object NonRepeatingRandom {
private var previous = -1
fun retrieve(max : Int = 9): Int {
if(max < 0) {
error("Only positive numbers")
}
if(max <= 1) {
// There is nothing random about 0 or 1, do not check against previous, just return
previous = max
return max
}
val rand = (1..9).random()
return if(rand == previous) {
retrieve(max) // recursive call if two subsequent retrieve() calls would return the same number
} else {
previous = rand // remember last random number
rand
}
}
}
fun main(args: Array<String>) {
repeat(1000) {
println(NonRepeatingRandom.retrieve())
}
}
I made a quick-and-dirty performance test, calling my "recursive" method 10 million times and calling the "filter" method 10 million times.
Recursive: 125 ms (10 mio calls)
Filter: 864 ms (10 mio calls)
Pre-fill & random shuffle approach:
class RandomIntIterator(
private val range: IntRange
) : Iterator<Int> {
private lateinit var iterator: Iterator<Int>
init { randomize() }
fun randomize() {
iterator = range.shuffled().iterator()
}
override fun hasNext() = iterator.hasNext()
override fun next() = iterator.next()
}
...
val rnd = RandomIntIterator(1..9)
...
// on button click
if (rnd.hasNext()) {
val num = rnd.next()
// use num
} else {
// renew (if needed)
rnd.randomize()
}
I like using Sequences to generate unending streams of values.
In this case we'll have to write custom code, as checking for repeated values is a stateful operation, and while Sequences has a distinct() stateful filter, it applies to all generated values - we only want it to apply to a limited window.
TL;DR:
class RandomImageLogic(
private val random: Random,
/** The number of sequential values that must be distinct */
noRepeatsLimit: Int = 2
) {
private val sourceValues: List<Int> = (0..9).toList()
private fun nextValue(vararg exclusions: Int): Int =
(sourceValues - exclusions.asList()).random(random)
private val randomInts: Iterator<Int> =
generateSequence({
// the initial value just has one random int
val next = nextValue()
ArrayDeque(listOf(next))
}) { previousValues ->
// generate the next value, excluding previous values
val nextValue = nextValue(*previousValues.toIntArray())
// limit the size of previousValues, if necessary
if (previousValues.size >= noRepeatsLimit)
previousValues.removeLastOrNull()
// add the generated value to the beginning of the deque
previousValues.addFirst(nextValue)
previousValues
}
.map {
// convert the Sequence to a list of ints,
// each element is the first item in the deque
it.first()
}
.iterator()
fun retrieve(): Int {
return randomInts.next()
}
}
Testing
Let's write a test first, to make sure our solution works. Kotest has a specific property based testing subproject, and this will let us cover a wide range of test cases very quickly.
So, I ran through the setup, and started setting up the test case.
Seeding RandomImageLogic
First I modified the RandomImageLogic class so that the random selection could be seeded with a provided Random.
import kotlin.random.Random
class RandomImageLogic(private val random: Random) {
fun retrieve(): Int {
return (1..9).random(random = random)
}
}
This will help us to create a Generator for the RandomImageLogic.
Testing all values
Now we can use Kotest to write a property-based test that will assert "for all sequential values, they are different"
import io.kotest.core.spec.style.FunSpec
import io.kotest.property.arbitrary.arbitrary
import io.kotest.property.forAll
class RandomImageLogicTest: FunSpec({
// This generator will create a new instance of `RandomImageLogic`
// and generate two sequential values.
val sequentialValuesArb = arbitrary { rs ->
val randomImageLogic = RandomImageLogic(rs.random)
val firstValue = randomImageLogic.retrieve()
val secondValue = randomImageLogic.retrieve()
firstValue to secondValue
}
test("expect sequential values are different") {
forAll(sequentialValuesArb) { (firstValue, secondValue) ->
firstValue != secondValue
}
}
})
Of course, the test fails.
Property failed after 4 attempts
Arg 0: (1, 1)
Repeat this test by using seed 1210584330919845105
Caused by org.opentest4j.AssertionFailedError: expected:<true> but was:<false>
So let's fix it!
Generating Sequences
As I said earlier, I really like Sequences. They're perfect for this use-case, where we have an infinite source of values.
To demonstrate how to make a Sequence, let's convert the existing code, and use an Iterator to fetch values.
class RandomImageLogic(private val random: Random) {
private val randomInts =
// generate a sequence using values from this lambda
generateSequence { (1..9).random(random = random) }
// use an iterator to fetch values
.iterator()
fun retrieve(): Int {
return randomInts.next()
}
}
This hasn't solved the problem yet - the Sequence only generates and provides one value at a time and so cannot do any filtering. Fortunately generateSequence() has an variant with nextFunction: (T) -> T?, where we can determine the next value based on the previous value.
If we use this constructor, and do a bit of refactoring to share the source values, and a util method to generate a next value while filtering out previous values...
private val sourceValues: List<Int> = (0..9).toList()
private fun nextValue(vararg exclusions: Int): Int =
(sourceValues - exclusions.asList()).random(random)
private val randomInts: Iterator<Int> =
generateSequence({ nextValue() }) { previousValue ->
nextValue(previousValue)
}
.iterator()
and now if we run the test, it passes!
Test Duration Result
expect sequential values are different 0.077s passed
Improvement: more than two distinct sequential values
What happens if you don't just want two sequential values to be distinct, but 3? Or even more? Let's make the 'no-repeated-values' limit configurable, and I think this will demonstrate why Sequences are a good solution.
class RandomImageLogic(
private val random: Random,
/** The number of sequential values that must be distinct */
noRepeatsLimit: Int = 2
) {
// ...
}
Testing
Once again, let's write a test to make sure things work as expected.
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.collections.shouldNotContainDuplicates
import io.kotest.property.Arb
import io.kotest.property.arbitrary.int
import io.kotest.property.checkAll
import kotlin.random.Random
class RandomImageLogicTest : FunSpec({
test("expect arbitrary sequential values are different") {
checkAll(Arb.int(), Arb.int(1..10)) { seed, noRepeatsLimit->
val randomImageLogic = RandomImageLogic(Random(seed), noRepeatsLimit)
val result = List(noRepeatsLimit) { randomImageLogic.retrieve() }
withClue("Result: $result") {
result shouldHaveSize noRepeatsLimit
result.shouldNotContainDuplicates()
}
}
}
})
And of course the test fails.
Property test failed for inputs
0) -459964888
1) 5
Caused by java.lang.AssertionError: Result: [3, 8, 0, 2, 8]
Collection should not contain duplicates
There's lots of options to make a Sequence stateful - again let's just look at one.
Sequence of values
Instead of a sequence of individual values, we can have a sequence where each element is a list of not only the current value, but also previously seen values.
Let's use ArrayDeque to store these values, because it's easy to add and remove values from the start and end.
Again, we use the same generateSequence constructor with a seedFunction and nextFunction - except this time each element is deque which stores all values, and in nextFunction we add new values to the start of the deque, trimming it if it's larger than the window size noRepeatsLimit
private val randomInts: Iterator<Int> =
generateSequence({
// the initial value just has one random int
val next = nextValue()
ArrayDeque(listOf(next))
}) { previousValues ->
// generate the next value, excluding previous values
val nextValue = nextValue(*previousValues.toIntArray())
// limit the size of previousValues, if necessary
if (previousValues.size >= noRepeatsLimit)
previousValues.removeLastOrNull()
// add the generated value to the beginning of the deque
previousValues.addFirst(nextValue)
previousValues
}
.map {
// convert the Sequence to a list of ints,
// each element is the first item in the deque
it.first()
}
.iterator()
And yup, the test passes!
Test Duration Result
expect arbitrary sequential values are different 0.210s passed
Storing state
It's important to think about how state will be stored. 'State' is required by RandomImageLogic to know whether a generated value is distinct.
In the Sequence implementation, it's stored internally, and so is specifically associated with an instance of RandomImageLogic. Maybe your application only has one instance of RandomImageLogic at any one time, in which case the state will always be up to date and will be shared between all invocations.
But what happens if there's more than one instance of RandomImageLogic? Or if there's multi-threading? Or if the RandomImageLogic instance is recreated?
The answers to these depend on the implementation and situation. From your question I suspect that it's not critically important that images never repeat, but I bring this up because it is important to think about.
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 have some doubts regarding the working on the subscribeOn operator. I read some article regarding this.
The observeOn is quite easy to understand, it changes only the downstram, and change affects to all the downstream.
But as told in the article subscribeOn can be put in any place in the stream because it affects only the time of subscription.:
To understand this , I did a samlpe and tried logging the thread at each point of time.
Observable.just("Hello")
.map(s -> {
Log.d(TAG, s + " in " + Thread.currentThread());
return 1;
})
.subscribeOn(Schedulers.newThread())
.map(integer -> {
Log.d(TAG, integer + " in " + Thread.currentThread());
return true;
})
.map(aBoolean -> {
Log.d(TAG, aBoolean + " in " + Thread.currentThread());
return 11.0;
})
.subscribeOn(Schedulers.computation())
.subscribe(aDouble -> {
Log.d(TAG, "accept in " + Thread.currentThread());
Log.d(TAG, "accept: " + aDouble);
});
The result is
Hello in Thread[RxNewThreadScheduler-1,5,main]
1 in Thread[RxNewThreadScheduler-1,5,main]
true in Thread[RxNewThreadScheduler-1,5,main]
accept in Thread[RxNewThreadScheduler-1,5,main]
accept: 11.0
Here twice I'm applying subscribeOn, but everytime the first added one seem to be applied throughout the stream.
Can anyone please explain in simple words how does it actually work, since I'm a beginner and hard to digest this!
Thanks in advance
subscribeOn: If you have multiple subscribeOn then the first one takes effect. If you want to change the Scheduler on the stream after making a subscribeOn, then take a look at observeOn
observeOn: It changes the Scheduler going downstream.
For example:
just("Some String") // Computation
.subscribeOn(Schedulers.computation()) // it changes scheduler to computation beginning from source to observer.
.map(str -> str.length()) // Computation
.observeOn(Schedulers.io) //change the scheduler from here till the observer
.map(length -> 2 * length) // io
.subscribe(number -> Log.d("", "Number " + number));// io
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
}
});
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