RxAndroid response of one call to make another request - operators

I'm new to RxAndroid and trying to chain responses.
I'm using this github API to retrieve data. Along with each issue there are comments link and events link associated with it which I want to fetch and update existing object with list of comments and events to form something like this.
[
issue: {
comments: [
{
.
.
},
{
.
.
}
]
events : [
{
.
.
},
{
.
.
}
]
]
]
I could retrieve initial response with following code
GitHubService gitHubService = ServiceFactory.createServiceFrom(GitHubService.class, GitHubService.ENDPOINT);
gitHubService.getIssuesList()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.map(issues -> Arrays.asList(issues))
.subscribe(adapter::add);
Now How do I retrieve comments and events before updating adapter ? I want to show 3 comments and 3 events as well.

Thanks to #Riccardo Ciovati for your example !
Here is my solution. and it works perfectly !
public static void getIssuesForRepo(final IssuesListAdapter adapter) {
GitHubService gitHubService = ServiceFactory.createServiceFrom(GitHubService.class, GitHubService.ENDPOINT);
gitHubService.getIssuesList()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.map(issues -> Arrays.asList(issues))
.flatMap(issues -> Observable.from(issues))
.filter(issue -> issue.getCommentsUrl() != null)
.flatMap(new Func1<Issue, Observable<Issue>>() {
#Override
public Observable<Issue> call(Issue issue) {
return gitHubService.getComments(issue.getNumber())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.map(comments -> {
issue.setCommentList(Arrays.asList(comments));
return issue;
});
}
})
.toList()
.subscribe(adapter::add);
}
where
public interface GitHubService {
String ENDPOINT = "https://api.github.com/";
#GET("repos/crashlytics/secureudid/issues")
Observable<Issue[]> getIssuesList();
#GET("repos/crashlytics/secureudid/issues/{number}/comments")
Observable<Comment[]> getComments(#Path("number") long number);
}

Related

Java reactor and variable scope

I am trying to get my head around variable propagation using reactor. I have a function as follow where I am trying to pass a variable named requestName from outside the map as follow:
public Mono<ResponseEntity<? extends Object>> myFunction(
final Object request, final String requestName) {
return this.client
.create(request)
.exchangeToMono(
response -> {
final HttpStatus status = response.statusCode();
return response
.bodyToMono(String.class)
.defaultIfEmpty(StringUtils.EMPTY)
.map(
body -> {
if (status.is2xxSuccessful()) {
log.info("{}", requestName);
return ResponseEntity.ok().build();
} else {
return ResponseEntity.badRequest().body(null);
}
});
})
.onErrorResume(ex -> Mono.just(buildErrorFromException(requestName, ex)));
}
or another example would be:
String myvar = "test"
return this.
.login()
.flatMap(
response ->
this.myservice(myvar))
.flatMap(
response2 ->
this.myservice2(myvar))
Is it appropriate ? Or would i need to wrap this function around a Mono.deferContextual and apply a contextView ?
Thanks a lot for your help.

How to return a Flux in async/reactive webclient request with subscribe method

I am using spring hexagonal architecture (port and adapter) as my application need to read the stream of data from the source topic, process/transforms the data, and send it to destination topic.
My application need to do the following actions.
Read the data (which will have the call back url)
Make an http call with the url in the incoming data (using webclient)
Get the a actual data and it needs to be transformed into another format.
Send the transformed data to the outgoing topic.
Here is my code,
public Flux<TargeData> getData(Flux<Message<EventInput>> message)
{
return message
.flatMap(it -> {
Event event = objectMapper.convertValue(it.getPayload(), Event.class);
String eventType = event.getHeader().getEventType();
String callBackURL = "";
if (DISTRIBUTOR.equals(eventType)) {
callBackURL = event.getHeader().getCallbackEnpoint();
WebClient client = WebClient.create();
Flux<NodeInput> nodeInputFlux = client.get()
.uri(callBackURL)
.headers(httpHeaders -> {
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
List<MediaType> acceptTypes = new ArrayList<>();
acceptTypes.add(MediaType.APPLICATION_JSON);
httpHeaders.setAccept(acceptTypes);
})
.exchangeToFlux(response -> {
if (response.statusCode()
.equals(HttpStatus.OK)) {
System.out.println("Response is OK");
return response.bodyToFlux(NodeInput.class);
}
return Flux.empty();
});
nodeInputFlux.subscribe( nodeInput -> {
SourceData source = objectMapper.convertValue(nodeInput, SourceData.class);
// return Flux.fromIterable(this.TransformImpl.transform(source));
});
}
return Flux.empty();
});
}
The commented line in the above code is giving the compilation as subscribe method does not allow return types.
I need a solution "without using block" here.
Please help me here, Thanks in advance.
I think i understood the logic. What do you may want is this:
public Flux<TargeData> getData(Flux<Message<EventInput>> message) {
return message
.flatMap(it -> {
// 1. marshall and unmarshall operations are CPU expensive and could harm event loop
return Mono.fromCallable(() -> objectMapper.convertValue(it.getPayload(), Event.class))
.subscribeOn(Schedulers.parallel());
})
.filter(event -> {
// 2. Moving the if-statement yours to a filter - same behavior
String eventType = event.getHeader().getEventType();
return DISTRIBUTOR.equals(eventType);
})
// Here is the trick 1 - your request below return Flux of SourceData the we will flatten
// into a single Flux<SourceData> instead of Flux<List<SourceData>> with flatMapMany
.flatMap(event -> {
// This WebClient should not be created here. Should be a singleton injected on your class
WebClient client = WebClient.create();
return client.get()
.uri(event.getHeader().getCallbackEnpoint())
.accept(MediaType.APPLICATION_JSON)
.exchangeToFlux(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
System.out.println("Response is OK");
return response.bodyToFlux(SourceData.class);
}
return Flux.empty();
});
})
// Here is the trick 2 - supposing that transform return a Iterable of TargetData, then you should do this and will have Flux<TargetData>
// and flatten instead of Flux<List<TargetData>>
.flatMapIterable(source -> this.TransformImpl.transform(source));
}

How to get Flux-Results into a set of a Mono-Bean

I have the following scenario: I have a ProductFamily that has a set of Products.
I load the ProduktFamily from the database and then I want to load its Products and insert them into ProduktFamily.products.
Easy in Spring MVC (because of JPA) but with Webflux I am struggeling.
I tried this, but it does not work. ProduktFamily.products-set is empty.
Service:
public Flux<ProduktfamilyRS> getAllProduktFamilies() {
return produktfamilyDatabaseFacade.findAll()
.map(produktfamilyEntity2 -> dtoMapper.produktfamilyEntityToProduktfamilyRS(produktfamilyEntity2)) // <-- Mapsctruct Mapper
.concatMap(produktfamilyRS -> loadProducts(produktfamilyRS));
}
private Mono<ProduktfamilieRS> loadProducts(ProduktfamilieRS produktfamilieRS) {
Flux<ProduktEntity> byProduktfamilieId = produktDatabaseFacade.findByProduktfamilieId(produktfamilieRS.getId());
Flux<ProduktRS> produktRSFlux = byProduktfamilieId.map(produktEntity -> dtoMapper.produktEntityToProduktRS(produktEntity));
return Mono.just(produktfamilieRS).map(produktfamilieRS1 -> {
produktRSFlux.all(produktRS -> produktfamilieRS1.getProdukte().add(produktRS));
produktRSFlux.subscribe();
return produktfamilieRS1;
});
}
ProduktfamilyDatabaseFacade:
public Flux<ProduktfamilyEntity> findAll() {
return produktfamilyRepository.findAll();
}
ProduktfamilyDatabaseFacade:
public Flux<ProduktEntity> findByProduktfamilyId(Long produktfamilyId) {
return produktRepository.findAllByProduktfamilyId(produktfamilyeId)
.doOnNext(produktEntity -> log.info("Found Produkt '" + produktEntity.getName() + "' für Produktfamilie"));
}
Is there an way to start from the Mono and then iterate through the Flux to add every Product to the Productfamily and then return the ProductfamilyRS?
Thank you
Ok got it working, but do not know if its a good way. Can somebody review?
public Flux<ProduktfamilieRS> getAllProduktFamilien() {
return produktfamilieDatabaseFacade.findAll()
.map(produktfamilieEntity2 -> dtoMapper.produktfamilieEntityToProduktfamilieRS(produktfamilieEntity2))
.concatMap(this::loadProdukteFlat);
}
private Mono<ProduktfamilieRS> loadProdukteFlat(ProduktfamilieRS produktfamilieRS) {
return produktDatabaseFacade.findByProduktfamilieId(produktfamilieRS.getId())
.doOnNext(produktEntity -> produktfamilieRS.getProdukte().add(dtoMapper.produktEntityToProduktRS(produktEntity)))
.then(Mono.just(produktfamilieRS));
}

Exposing BLOC streams via fields, methods, or getter

I am using the BLOC pattern for my latest Flutter app and I started out using something like this for my output streams:
class MyBloc {
// Outputs
final Stream<List<Todo>> todos;
factory MyBloc(TodosInteractor interactor) {
final todosController = BehaviorSubject<List<Todo>>()
..addStream(interactor.todos);
return MyBloc._(todosController);
}
MyBloc._(this.todos);
}
but slowly I found myself doing something more like this, using a method (or getter) after awhile:
class MyBloc {
final TodosInteractor _interactor;
// Outputs
Stream<List<Todo>> todos(){
return _interactor.todos;
}
MyBloc(this._interactor) { }
}
For people who want to see... getter for todos in TodosInteractor:
Stream<List<Todo>> get todos {
return repository
.todos()
.map((entities) => entities.map(Todo.fromEntity).toList());
}
When I look at the differing code, I see that the first example uses a field versus a method to expose the stream but I couldn't figure out why I would choose one over the other. It seems to me that creating another controller just to push through the stream is a little much... Is there a benefit to this other than being immutable in my todos stream definition? Or am I just splitting hairs?
Well maybe this will not be a best answer but it is a good practice expose your output stream using get methods. Below a example of a bloc class that i have written to a project using RxDart.
class CityListWidgetBloc {
final _cityInput = PublishSubject<List<Cidade>>();
final _searchInput = new PublishSubject<String>();
final _selectedItemsInput = new PublishSubject<List<Cidade>>();
// exposing stream using get methods
Observable<List<Cidade>> get allCities => _cityInput.stream;
Observable<List<Cidade>> get selectedItems => _selectedItemsInput.stream;
List<Cidade> _searchList = new List();
List<Cidade> _selectedItems = new List();
List<Cidade> _mainDataList;
CityListWidgetBloc() {
//init search stream
_searchInput.stream.listen((searchPattern) {
if (searchPattern.isEmpty) {
_onData(_mainDataList); // resend local data list
} else {
_searchList.clear();
_mainDataList.forEach((city) {
if (city.nome.toLowerCase().contains(searchPattern.toLowerCase())) {
_searchList.add(city);
}
});
_cityInput.sink.add(_searchList);
}
});
}
//getting data from firebase
getCity( {#required String key}) {
FirebaseStateCityHelper.getCitiesFrom(key, _onData);
//_lastKey = key;
}
searchFor(String pattern) {
_searchInput.sink.add(pattern);
}
void _onData(List<Cidade> list) {
_mainDataList = list;
list.sort((a, b) => (a.nome.compareTo(b.nome)));
_cityInput.sink.add(list);
}
bool isSelected(Cidade item) {
return _selectedItems.contains(item);
}
void selectItem(Cidade item) {
_selectedItems.add(item);
_selectedItemsInput.sink.add(_selectedItems);
}
void selectItems(List<Cidade> items){
_selectedItems.addAll( items);
_selectedItemsInput.sink.add( _selectedItems );
}
void removeItem(Cidade item) {
_selectedItems.remove(item);
_selectedItemsInput.sink.add(_selectedItems);
}
dispose() {
_cityInput.close();
_searchInput.close();
_selectedItemsInput.close();
}
}

Customize binding in ASP.NET Web Api

I am stuck with following problem in ASP.NET Web Api. Let say I have following code in my ApiController:
public void Post(Person person)
{
// Handle the argument
}
What I would like to do is to accept following JSON request:
{
"person": {
"name": "John Doe",
"age": 27
}
}
I would like to go around creating some holding object for each model just to properly bind incoming data. In previous version of MVC, it was possible to define something like Prefix to solve this.
Let me report that I have been able to solve this implementing CustomJsonMediaTypeFormatter:
public class EmberJsonMediaTypeFormatter : JsonMediaTypeFormatter
{
public override System.Threading.Tasks.Task<object> ReadFromStreamAsync(
Type type,
System.IO.Stream readStream,
System.Net.Http.HttpContent content,
IFormatterLogger formatterLogger)
{
return base.ReadFromStreamAsync(
typeof(JObject),
readStream,
content,
formatterLogger).ContinueWith<object>((task) =>
{
var data = task.Result as JObject;
var prefix= type.Name.ToLower();
if (data[prefix] == null)
{
return GetDefaultValueForType(type);
}
var serializer = JsonSerializer.Create(SerializerSettings);
return data[prefix].ToObject(type, serializer);
});
}
}
and replacing default JsonMediaTypeFormatter in GlobalConfiguration.