RxJava PublishSubject subscribe doesn't give anything. Something's wtong with subscription? - java

Learning RxJava so will be appreciate for any advice.
Need to receive objects when calculation is ready, so I made PublishSubject in models method:
public PublishSubject<BaseUnit> exec(int inputNumber) {
if (unitList.size() > 0) {
for (BaseUnit unit : unitList) {
unit.setInProgress();
}
}
PublishSubject<BaseUnit> subject = PublishSubject.create();
list = new ArrayList<>();
populateList(inputNumber).
subscribeOn(Schedulers.from(Executors.newFixedThreadPool(ThreadPool.getPoolSize())))
.subscribe(calculatedList -> {
list = calculatedList;
for (List<Integer> elem : list) {
for (ListOperationName operationName : ListOperationName.values()) {
ListUnit unit = new ListUnit(operationName, elem, 0);
calculate(unit);
unitList.add(unit);
subject.onNext(unit);
}
}
}, error -> Log.d("ERROR", error.toString()));
return subject;
}
public Observable<ArrayList<List<Integer>>> populateList(int inputNumber) {
return Observable.fromCallable(() -> {
ArrayList<List<Integer>> list = new ArrayList<>();
Integer[] populatedArray = new Integer[inputNumber];
Arrays.fill(populatedArray, insertValue);
list.add(new ArrayList<>(Arrays.asList(populatedArray)));
list.add(new LinkedList<>(Arrays.asList(populatedArray)));
list.add(new CopyOnWriteArrayList<>(Arrays.asList(populatedArray)));
return list;
});
}
And then trying to subscribe in presenter:
public void calculate(int inputNumber) {
fragment.showAllProgressBars();
repository.exec(inputNumber)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.from(Executors.newFixedThreadPool(ThreadPool.getPoolSize())))
.subscribe(unit -> {
Log.d("PRESENTER RESULT", unit.toString());
fragment.setCellText(unit.getViewId(), unit.getTimeString());
}, error -> Log.d("PRESENTER ERROR", error.toString()));
}
This gives me nothing. But if I use ReplaySubject - it gives me all results, but seems like it uses only one thread. So I assume there is somethings wrong I made with subscription and it should be somewhere earlier.
I need to use exactly PublishSubject for giving me results as they are ready using multiple threads.
How to fix that? Or maybe there is other problem?

Related

Always return HttpStatus 200 (OK)

We obtain a list of data from a SQL Server database. I want to return the list when there is data, but when there is not, I want to return a "No Content" status. My code:
public class Main {
public static void main(String[] args) {
var main = new Main();
var result = main.controllerMethod();
System.out.println("Result: " + result.blockingGet());
}
public Flowable<Person> personList(){
List<Person> repositoryList = List.of();
return repositoryList
.stream()
.collect(Collectors.collectingAndThen(Collectors.toList(), list -> {
if(list.isEmpty()) return Flowable.empty();
else return Flowable.fromIterable(list);
}));
}
public Maybe<ResponseEntity<Flowable<Person>>> controllerMethod(){
var httpStatus =
new AtomicReference<>(HttpStatus.OK);
return Maybe.just(personList()
.switchIfEmpty(subs -> Completable.fromAction(() ->
httpStatus.set(HttpStatus.NO_CONTENT)).toFlowable()))
.map(person -> ResponseEntity.status(httpStatus.get())
.body(person));
}
}
result:
Result: <200 OK OK,io.reactivex.rxjava3.internal.operators.flowable.FlowableSwitchIfEmpty#35f983a6,[]>
It seems that you expect httpStatus.set(HttpStatus.NO_CONTENT) to be run, and after running, the status can be read with the correct statuscode. I'm not very familiar with reactive, but by looking at Completable.java I don't think the Action that you provide in Completable::fromAction is run right away.
There is probably a more reactive-like way to solve the problem, but I can't help you there. Instead, here's a solution that might work (or at least get you going in the right direction). Just set the value of the status explicitly yourself before returning your Flowable:
public Flowable<Person> personList(AtomicReference<HttpStatus> reference) {
List<Person> repositoryList = List.of();
return repositoryList
.stream()
.collect(Collectors.collectingAndThen(Collectors.toList(), list -> {
if (list.isEmpty()) {
reference.set(HttpStatus.NOT_FOUND);
return Flowable.empty();
}
return Flowable.fromIterable(list);
}));
}
public Maybe<ResponseEntity<Flowable<Person>>> controllerMethod() {
var httpStatus = new AtomicReference<>(HttpStatus.OK);
return Maybe.just(personList(httpStatus))
.map(person -> ResponseEntity.status(httpStatus.get()))
.body(person));
}

Subscribe to flux from inside subscribe in Spring webFlux java

I have written a logic using spring reactor library to get all operators and then all devices for each operator (paginated) in async mode.
Created a flux to get all operator and then subscribing to it.
final Flux<List<OperatorDetails>> operatorDetailsFlux = reactiveResourceProvider.getOperators();
operatorDetailsFlux
.subscribe(operatorDetailsList -> {
for (final OperatorDetails operatorDetails : operatorDetailsList) {
getAndCacheDevicesForOperator(operatorDetails.getId());
}
});
Now, for each operator I'm fetching the devices which requires multiple subscriptions to get device mono which gets all pages async by subscribing to the MONO.
private void getAndCacheDevicesForOperator(final int operatorId) {
Mono<DeviceListResponseEntity> deviceListResponseEntityMono = reactiveResourceProvider.getConnectedDeviceMonoWithRetryAndErrorSpec(
operatorId, 0);
deviceListResponseEntityMono.subscribe(deviceListResponseEntity -> {
final PaginatedResponseEntity PaginatedResponseEntity = deviceListResponseEntity.getData();
final long totalDevicesInOperator = PaginatedResponseEntity.getTotalCount();
int deviceCount = PaginatedResponseEntity.getCount();
while (deviceCount < totalDevicesInOperator) {
final Mono<DeviceListResponseEntity> deviceListResponseEntityPageMono = reactiveResourceProvider.getConnectedDeviceMonoWithRetryAndErrorSpec(
operatorId, deviceCount);
deviceListResponseEntityPageMono.subscribe(deviceListResponseEntityPage -> {
final List<DeviceDetails> deviceDetailsList = deviceListResponseEntityPage.getData()
.getItems();
// work on devices
});
deviceCount += DEVICE_PAGE_SIZE;
}
});
}
This code works fine. But my question is it a good idea to subscribe to mono from inside subscribe?
I broke it down to two flows 1st getting all operators and then getting all devices for each operator.
For pagination I'm using Flux.expand to extract all pages.
public Flux<OperatorDetails> getAllOperators() {
return getOperatorsMonoWithRetryAndErrorSpec(0)
.expand(paginatedResponse -> {
final PaginatedEntity operatorDetailsPage = paginatedResponse.getData();
if (morePagesAvailable(operatorDetailsPage) {
return getOperatorsMonoWithRetryAndErrorSpec(operatorDetailsPage.getOffset() + operatorDetailsPage.getCount());
}
return Mono.empty();
})
.flatMap(responseEntity -> fromIterable(responseEntity.getData().getItems()))
.subscribeOn(apiScheduler);
}
public Flux<Device> getAllDevices(final int opId, final int offset) {
return getConnectedDeviceMonoWithRetryAndErrorSpec(opId, offset)
.expand(paginatedResponse -> {
final PaginatedEntity deviceDetailsPage = paginatedResponse.getData();
if (morePagesAvailabile(deviceDetailsPage)) {
return getConnectedDeviceMonoWithRetryAndErrorSpec(opId,
deviceDetailsPage.getOffset() + deviceDetailsPage.getCount());
}
return Mono.empty();
})
.flatMap(responseEntity -> fromIterable(responseEntity.getData().getItems()))
.subscribeOn(apiScheduler);
}
Finally I'm creating a pipeline and subscribing to it to trigger the pipeline.
operatorDetailsFlux
.flatMap(operatorDetails -> {
return reactiveResourceProvider.getAllDevices(operatorDetails.getId(), 0);
})
.subscribe(deviceDetails -> {
// act on devices
});

Processing a list of rx.Observables

I want to process a List<Observable<MyResponse>>. How can I do this?Had a look at Observable.zip() but unable to arrive at a conclusion.
Code snippet of what I wanna do:
List<String> myList = myMethod(); // myMethod() returns a List<String> of any possible length.
List<Observable<MyResponse>> observables = new ArrayList<>();
for (String value : myList) {
observableList.add(myAPI.getData(value))
}
// How to? : For each Observable<MyResponse> in observables call a method myProcessor(MyResponse, ...)
You can flatMap the myList:
Flowable.fromIterable(myList)
.flatMap(value -> myAPI.getData(value))
.doOnNext(myResponse -> { /* ... */ })
...
I tried again & the following worked for me:
List<Observable<MyResponse>> observables = new ArrayList<>();
List<String> values = getValues();
for (String value : values) {
observables.add(myAPI.myMethod());
}
Observable.zip(observables, new FuncN() {
#Override
public Object call(Object... args) {
for (Object arg : args) {
process((MyResponse(arg));
}
return null;
}
}).toBlocking().first();

Convert Observable of List to List of Observables and merge in RxJava

I'm learning Java with Android by creating Hacker News reader app.
What I'm trying to do is:
Send a request to /topstories, return Observable<List<int>>, emit when
request finishes.
Map each storyId to Observable<Story>
Merge Observables into one entity, which emits List<Story>, when all requests finishes.
And to the code:
private Observable<Story> getStoryById(int articleId) {
BehaviorSubject<Story> subject = BehaviorSubject.create();
// calls subject.onNext on success
JsonObjectRequest request = createStoryRequest(articleId, subject);
requestQueue.add(request);
return subject;
}
public Observable<ArrayList<Story>> getTopStories(int amount) {
Observable<ArrayList<Integer>> topStoryIds = (storyIdCache == null)
? fetchTopIds()
: Observable.just(storyIdCache);
return topStoryIds
.flatMap(id -> getStoryById(id))
// some magic here
}
Then we would use this like:
getTopStories(20)
.subscribe(stories -> ...)
You can try something like that
Observable<List<Integers>> ids = getIdsObservable();
Single<List<Story>> listSingle =
ids.flatMapIterable(ids -> ids)
.flatMap(id -> getStoryById(id)).toList();
Then you can subscribe to that Single to get the List<Story>
Please have a look at my solution. I changed your interface to return a Single for getStoryById(), because it should only return one value. After that, I created a for each Story a Single request and subscribed to all of them with Single.zip. Zip will execute given lambda, when all Singles are finished. On drawback is, that all requestes will be fired at once. If you do not want this, I will update my post. Please take into considerations that #elmorabea solution will also subscribe to the first 128 elements (BUFFER_SIZE = Math.max(1, Integer.getInteger("rx2.buffer-size", 128));), and to the next element when one finishes.
#Test
void name() {
Api api = mock(Api.class);
when(api.getTopStories()).thenReturn(Flowable.just(Arrays.asList(new Story(1), new Story(2))));
when(api.getStoryById(eq(1))).thenReturn(Single.just(new Story(888)));
when(api.getStoryById(eq(2))).thenReturn(Single.just(new Story(888)));
Flowable<List<Story>> listFlowable =
api.getTopStories()
.flatMapSingle(
stories -> {
List<Single<Story>> collect =
stories
.stream()
.map(story -> api.getStoryById(story.id))
.collect(Collectors.toList());
// possibly not the best idea to subscribe to all singles at the same time
Single<List<Story>> zip =
Single.zip(
collect,
objects -> {
return Arrays.stream(objects)
.map(o -> (Story) o)
.collect(Collectors.toList());
});
return zip;
});
TestSubscriber<List<Story>> listTestSubscriber =
listFlowable.test().assertComplete().assertValueCount(1).assertNoErrors();
List<List<Story>> values = listTestSubscriber.values();
List<Story> stories = values.get(0);
assertThat(stories.size()).isEqualTo(2);
assertThat(stories.get(0).id).isEqualTo(888);
assertThat(stories.get(1).id).isEqualTo(888);
}
interface Api {
Flowable<List<Story>> getTopStories();
Single<Story> getStoryById(int id);
}
static class Story {
private final int id;
Story(int id) {
this.id = id;
}
}

Require some inputs to make piece of code better with Java-8

I have small piece of code (POJO) with java-8. My question is can I do something better with using java-8? I have mentioned some comments in class.
Assume that I have Person, Standard and Subject pojos and also do not worry about equals and hashCode methods, I just want inputs with respect to Java-8.
package com.java8.learn.domain;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* #author Vishal.Zanzrukia
*
*/
public class Teacher extends Person {
private Map<Standard, Set<Subject>> standardWiseSubjects;
public Map<Standard, Set<Subject>> getStandardWiseSubjects() {
return standardWiseSubjects;
}
public void addStandardWiseSubject(Subject subject, Standard standard) {
if (this.standardWiseSubjects == null) {
this.standardWiseSubjects = new HashMap<>();
}
if (this.standardWiseSubjects.containsKey(standard)) {
this.standardWiseSubjects.get(standard).add(subject);
} else {
Set<Subject> subjects = new HashSet<>();
subjects.add(subject);
this.standardWiseSubjects.put(standard, subjects);
}
}
// I need some inputs over here.
public Map<Subject, Set<Standard>> getSubjectWiseStandards() {
if (this.standardWiseSubjects == null) {
return null;
}
Map<Subject, Set<Standard>> output = new HashMap<>();
this.standardWiseSubjects.values().parallelStream().flatMap(set -> set.stream()).forEach(subject -> {
Set<Standard> standards = new HashSet<>();
this.standardWiseSubjects.forEach((standard, subjects) -> {
// TODO can I convert this if condition into predicate?
if (subjects.contains(subject)) {
standards.add(standard);
}
});
output.put(subject, standards);
});
return output;
}
}
The addStandardWiseSubject method can be simplified using the computeIfAbsent method:
public void addStandardWiseSubject(Subject subject, Standard standard) {
if (this.standardWiseSubjects == null) {
this.standardWiseSubjects = new HashMap<>();
}
this.standardWiseSubjects.computeIfAbsent(standard,
s -> new HashSet<>()).add(subject);
}
The getSubjectWiseStandards() is trickier: you need to invert the map. It can be done producing the intermediate pairs of all subjects and corresponding standards (I used the AbstractMap.SimpleEntry class to represent such pair):
public Map<Subject, Set<Standard>> getSubjectWiseStandards() {
if (this.standardWiseSubjects == null) {
return null;
}
return this.standardWiseSubjects.entrySet().parallelStream()
.<Map.Entry<Subject, Standard>> flatMap(
e -> e.getValue().stream()
.map(st -> new AbstractMap.SimpleEntry<>(st, e.getKey())))
.collect(Collectors.groupingBy(e -> e.getKey(),
Collectors.mapping(e -> e.getValue(),
Collectors.toSet())));
}
This may look simpler using the EntryStream class of my StreamEx library:
public Map<Subject, Set<Standard>> getSubjectWiseStandards() {
if (this.standardWiseSubjects == null) {
return null;
}
return EntryStream.of(this.standardWiseSubjects).parallel()
.invert().flatMapKeys(Set::stream).groupingTo(HashSet::new);
}
Please note that forEach with parallel stream should be used with extreme care as it can be executed for different elements in parallel. Your solution may work incorrectly as you modify the HashMap (which is not thread-safe) from this forEach operation.
It should be possible like below
Map<Standard, Set<Subject>> map = new HashMap<Standard, Set<Subject>>();
Map<Subject, Set<Standard>> reverseMap = new HashMap<Subject, Set<Standard>>();
HashSet<Subject> std1Subjects = new HashSet<Subject>();
std1Subjects.add(new Subject("English"));
std1Subjects.add(new Subject("Maths"));
std1Subjects.add(new Subject("Science"));
HashSet<Subject> std2Subjects = new HashSet<Subject>();
std2Subjects.add(new Subject("Hindi"));
std2Subjects.add(new Subject("Gujarati"));
std2Subjects.add(new Subject("Maths"));
HashSet<Subject> std3Subjects = new HashSet<Subject>();
std3Subjects.add(new Subject("Sanskrit"));
std3Subjects.add(new Subject("Science"));
std3Subjects.add(new Subject("Maths"));
map.put(new Standard("1"), std1Subjects);
map.put(new Standard("2"), std2Subjects);
map.put(new Standard("3"), std3Subjects);
//1.
map.keySet().forEach((std) -> map.get(std).forEach((sub)->{
if(reverseMap.containsKey(sub)){
reverseMap.get(sub).add(std);
}else{
HashSet<Standard> standardSet = new HashSet<Standard>();
standardSet.add(std);
reverseMap.put(sub, standardSet);
}
}));
reverseMap
.forEach((k, v) -> System.out.println("k-" + k + " , v-" + v));
And also like below
map.keySet().forEach((std) -> map.get(std).forEach((sub)->{
reverseMap.computeIfAbsent(sub,standard-> new HashSet<>()).add(std);
reverseMap.computeIfPresent(sub, (subject,stdSet) -> reverseMap.get(subject)).add(std);
}));

Categories

Resources