What is the purpose of flatMapPublisher ?
return factory.retrieveDiskDataStore().isCached()
.flatMapPublisher {
factory.retrieveDataStore(it).getData(token)
}
.flatMap {
Flowable.just(if (it is PickupListDataModel) mapper.mapFromData(it) else null)
}
.flatMap {
saveData(it).toSingle { it }.toFlowable()
}
In this code, factory.retrieveDiskDataStore().isCached() checks whether the information is stored in database or not.
If not, then following code executes
.flatMapPublisher {
factory.retrieveDataStore(it).getData(token)
}
From the JavaDocs:
Returns a Flowable that emits items based on applying a specified function to the item emitted by the source Single, where that function returns a Publisher.
In other terms, a Single will succeed with a value which you'd like to turn into a sequence of values generated by a Publisher (i.e., some Flowable, Flux or other standard Reactive Streams source) and have the items of that.
Related
I have one code snippet, which is calling 2 different services based on some a if condition. And both the services return CompletableFuture<Optional<SomeObject>>. Following is the code logic looks like
if(someCondition){
CompletableFuture<Optional<SomeObjectType1>> = service1.call();
}else{
CompletableFuture<Optional<SomeObjectType2>> = service2.call();
}
And both SomeObjectType1 and SomeObjectType2 have a String inside it, which is of my interest. My current code looks like this:
private ContentWrapper getContentWrapper(input1, input2, ....) {
String content = null;
if (some_condition is true) {
List<Object_Type_1> list = service1.fetchTheCompletableFuture(..... inputs...)
.join()
.map(ListOutput::getList)
.orElse(null);
if (CollectionUtils.isNotEmpty(list)) {
content = list.get(0).getContent();
}
} else {
content = service2
.fetchTheCompletableFuture(..... inputs...)
.join()
.map(RenderedContent::getContent)
.orElse(null);
}
return content != null ? new ContentWrapper(content) : null;
}
Now my question is, can this if-else clause be removed or make it more clear by using lambdas. I am new in lambdas and does not have very good idea on this.
I am not sure whether the code below even compiles due to the vagueness.
private ContentWrapper getContentWrapper(input1, input2, ....) {
Optional<RenderedContent> content = some_condition
? service1
.fetchTheCompletableFuture(..... inputs...)
.join()
.map(ListOutput::getList)
.stream()
.findFirst()
: service2
.fetchTheCompletableFuture(..... inputs...)
.join();
}
return content
.map(RenderedContent::getContent)
.map(ContentWrapper::new).orElse(null);
}
The first service seems to yield a list of RenderedContent of which to take the first if there is one.
The second service may yield a Rendered content immediately.
So you can join the if-else to an Optional<RenderedContent>.
The map(RenderedContent::getContent) will yield Optional.empty() if it was empty to begin with. Otherwise getContent is called and wrapped in an Optional.
If present new ContentWrapper(...) might be called.
Notice much may fail, like getContent returning null (though there is an Optional.ofNullable.
Nevertheless Streams may be very expressive.
I would avoid using null in favor of Optional as that plays better together.
I need to implement function that returns Mono< Array< ProcessedObject>>. As argument it takes list of objects and process them with function that returns Mono< ProcessedObject>. Function needs to keep original order, meaning first element on returned list must be created from first element from argument list. So far i have following solution but it doesn't keep required order. Is it even possible with Flux?
private fun createItems(objects: List<Someobjects>): Mono<Array<ProcessedObject>> {
return Flux.fromIterable(objects)
.flatMap {
processObject(it)
}.collectList().map { it.toTypedArray() }
}
Edit: to clarify a little processObject returns Mono< ProcessedObject>
You can try with concatMap instead of flatMap.
Here is a link for the Docu https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html#concatMap-java.util.function.Function-
private fun createItems(objects: List<Someobjects>): Mono<Array<ProcessedObject> {
return Flux.fromIterable(objects)
.concatMap {
processObject(it)
}.collectList().map { it.toTypedArray() }
}
The difference between flatMap and concatMap is that the later preserves the original order.
I am looking for what is the recommended practice in rxjava2 to handle a case where one flowable leads to conditional behaviors.
More concretely, I have a Maybe<String> for which I want to Update the String on the database if the String exists or, if it doesn't exists I want to create a new String and save it on the database.
I thought of the below but obviously it is not what I am looking for:
Maybe<String> source = Maybe.just(new String("foo")); //oversimplified source
source.switchIfEmpty(Maybe.just(new String("bar"))).subscribe(result ->
System.out.println("save to database "+result));
source.subscribe(result -> System.out.println("update result "+result));
The above obviously produces
save to database foo
update result foo
I tried also the below which gives the expected result but still feel it's... weird.
Maybe<String> source = Maybe.just(new String("foo")); //oversimplified source
source.switchIfEmpty(Maybe.just(new String("bar")).doOnSuccess(result ->
System.out.println("save to database "+result))).subscribe();
source.doOnSuccess(result -> System.out.println("update result "+result)).subscribe();
How can I have an action for when the result exists and when it doesn't exists? How is that use case supposed to be handled in rxjava2?
Update 01
I tried the below and it looks cleaner than what I came up with above. Note sure it is recommended rxjava2 practice however...
Maybe.just(new String("foo"))
.map(value -> Optional.of(value))
.defaultIfEmpty(Optional.empty())
.subscribe(result -> {
if(result.isPresent()) {
System.out.println("update result "+result);
}
else {
System.out.println("save to database "+"bar");
}
});
You have the isEmpty() operator that will return you Boolean if the Maybe source is empty or not, and then you can flatMap it and write a if else statement depending on that Boolean
This is a common pattern in our code as well, though in our case the choices are themselves async. You can't get quite the right semantic by simply composing flatMapX and switchIfEmpty (in either order), so I am curious why this isn't part of the API.
Here's what we're doing for now (this example for when the 2 options are both Completables, we have similar things for the other types as well):
public static <T> Completable flatMapCompletable(Maybe<T> target,
#ClosureParams(FirstParam.FirstGenericType.class)
Closure<? extends CompletableSource> completableSupplier,
Supplier<CompletableSource> emptySupplier) {
Maybe<T> result = target.cache();
return result.isEmpty().flatMapCompletable(empty -> {
if (empty) {
return emptySupplier.get();
} else {
return result.flatMapCompletable(completableSupplier::call);
}
});
}
We're using Groovy, so we package these up as extension methods. I'm not thrilled with the need to use cache() so I'm wondering if there is a better alternative. From looking at the code, an operator which basically combines flatMapX and switch looks like it wouldn't be too hard (but I feel like I'm missing something).
Try something like this. checkDB can return a Maybe or Single or whatever which emits either an optional or a wrapper Object.
checkDB(String)
.flatMap(s -> {
if (s.isPresent()) {
return updateDB(s.get());
} else {
return insertDB("new String");
}
})
There is an solution using the flatMap call with 3 params
fun addOrUpdate(message: LocalMessage): Single<LocalMessage> {
return getById(message.id) // returns Maybe
.flatMap(
Function {
update(message) // onSuccess update call returns Single
},
Function {
Single.error(it) // onError
},
Callable {
add(message) // onComplete add call returns Single
}
)
}
}
Or shorter version
fun addOrUpdate(message: LocalMessage): Single<LocalMessage> {
return getById(message.id) // returns Maybe
.flatMap(
{
update(message) // onSuccess update call returns Single
},
{
Single.error(it) // onError
},
{
add(message) // onComplete add call returns Single
}
)
}
}
I am currently using RxJava on Android with Kotlin, but I have a problem and I can't solve without using toBlocking().
I have method in employee service which returns an Observable>:
fun all(): Observable<List<Employee>>
This is all and good since this Observable emits the new list of employees whenever an employee changes. But I'd like to generate a PDF file from the employees, which obviously doesn't need to run everytime an employee changes. Also, I'd like to return a Completable object from my PDF generator method. I want to add a header to my PDF, and then iterate through the employees and calculate the wage of each employee, which also returns an Observable, and this is the place where I am using toBlocking right now. My current approach is this:
private fun generatePdf(outputStream: OutputStream): Completable {
return employeeService.all().map { employees ->
try {
addHeaderToPDF()
for (i in employees) {
val calculated = employeeService.calculateWage(i.id).toBlocking().first()
// Print calculated to PDF....
}
addFooterToPDF()
return #map Completable.complete()
}
catch (e: Exception) {
return #map Completable.error(e)
}
}.first().toCompletable()
Is there any way to make this code a little cleaner using RxJava?
Thanks in advance!
Disclaimer: This answer is a work in progress.
Basic premise: If you have blocking in the stream, you're doing it wrong.
Note: No state must leave the observable lambda.
Step 1: Stream the whole data set
The input is a stream of employees. For each employee you need to get one wage. Let's make it into one stream.
/**
* #param employeesObservable
* Stream of employees we're interested in.
* #param wageProvider
* Transformation function which takes an employee and returns a [Single] of their wage.
* #return
* Observable stream spitting individual [Pair]s of employees and their wages.
*/
fun getEmployeesAndWagesObservable(
employeesObservable: Observable<Employee>,
wageProvider: Function<Employee, Single<Int>>
): Observable<Pair<Employee, Int>>? {
val employeesAndWagesObservable: Observable<Pair<Employee, Int>>
// Each Employee from the original stream will be converted
// to a Single<Pair<Employee, Int>> via flatMapSingle operator.
// Remember, we need a stream and Single is a stream.
employeesAndWagesObservable = employeesObservable.flatMapSingle { employee ->
// We need to get a source of wage value for current employee.
// That source emits a single Int or errors.
val wageForEmployeeSingle: Single<Int> = wageProvider.apply(employee)
// Once the wage from said source is loaded...
val employeeAndWageSingle: Single<Pair<Employee, Int> = wageForEmployeeSingle.map { wage ->
// ... construct a Pair<Employee, Int>
employee to wage
}
// This code is not executed now. It will be executed for each Employee
// after the original Observable<Employee> starts spitting out items.
// After subscribing to the resulting observable.
return#flatMapSingle employeeAndWageSingle
}
return employeesAndWagesObservable
}
What's going to happen when you subscribe:
Take an employee from source.
Fetch wage of an employee.
Spit out a pair of employee and their wage.
This repeats until employeesObservable signals onComplete or something fails with onError.
Used operators:
flatMapSingle: Converts an actual value into a new Single stream of some transformed value.
map: Converts an actual value into some other actual value (no nested streams).
Hee's how you'd hook it up to your code:
fun doStuff() {
val employeesObservable = employeeService.all()
val wageProvider = Function<Employee, Single<Int>> { employee ->
// Don't listen to changes. Take first wage and use that.
employeeService.calculateWage(employee.id).firstOrError()
}
val employeesAndWagesObservable =
getEmployeesAndWagesObservable(employeesObservable, wageProvider)
// Subscribe...
}
Used operators:
first: Take the first item from observable and turn it into a Single stream.
timeout: A good idea would be to .timeout the wage if you're getting it over network.
Next steps
Option 1: End here
Don't subscribe, call
val blockingIterable = employeesAndWagesObservable.blockingIterable()
blockingIterable.forEach { ... }
and process each item in a synchronous fashion. Sit back, figure out next steps, watch presentations, read examples.
Option 2: Add layers
.map each of these Pair<Employee, Int> to some abstract PDF building block.
Turn your header and footer printers to Observables via Observable.fromCallable { ... }, have them return PDF building blocks too.
Merge all of these in a sequential manner via Observable.concat(headerObs, employeeDataObs, footerObs)
.subscribe to this result and start writing the PDF building blocks to a PDF writer.
TODO:
Figure out a way to initialize the PDF writer lazily on subscription (not before building the stream),
Delete output on error,
Close output stream on complete or on error.
I came up with this:
return employeeService.all().first()
.doOnSubscribe { addHeaderToPDF() }
.flatMapIterable { it }
.flatMap { employeeService.calculateWage(it.id).first() }
.doOnNext { printEmployeeWage(it) }
.doOnCompleted { addFooterToPDF }
.toCompletable()
Is this how it is supposed to be done? :)
I have a chain of operators on an RxJava observable. I'd like to be able to apply one of two operators depending on a boolean value without "breaking the chain".
I'm relatively new to Rx(Java) and I feel like there's probably a more idiomatic and readable way of doing this than my current approach of introducing a temporary variable.
Here's a concrete example, buffering items from an observable if a batch size field is non-null, otherwise emitting a single batch of unbounded size with toList():
Observable<Item> source = Observable.from(newItems);
Observable<List<Item>> batchedSource = batchSize == null ?
source.toList() :
source.buffer(batchSize);
return batchedSource.flatMap(...).map(...)
Is something like this possible? (pseudo-lambdas because Java):
Observable.from(newItems)
.applyIf(batchSize == null,
{ o.toList() },
{ o.buffer(batchSize) })
.flatMap(...).map(...)
You can use compose(Func1) to stay in-sequence but do custom behavior
source
.compose(o -> condition ? o.map(v -> v + 1) : o.map(v -> v * v))
.filter(...)
.subscribe(...)
You can also use filter operator with defaultIfEmpty if you wish to emit single value or switchIfEmpty if you wish to emit multiple values using another Observable.
val item = Observable.just("ABC")
item.filter { s -> s.startsWith("Z") }
.defaultIfEmpty("None")
.subscribe { println(it) }