I need some help to pass parameter from one compose to another. I want to pass the labelParemeters in the second compose into the last compose, as I have shown in the code below.
public Future<JsonArray> startTest(int jobID, RoutingContext context) {
LOG.info("-----INside startTest() method-----");
return jobDao.getJob(jobID, context)
.compose(job -> productDao.getLabelParameters(job, context))
.compose(labelParameters -> jobDao.getJobParentChildForPrint(jobID, context, true, labelParameters))
.compose(parentChildSerials -> {
LOG.debug(" Future Returned Job Parent-Child to Print: ");
prepareSerialForPrint(parentChildSerials, labelParameters); //Pass Here
return Future.succeededFuture(parentChildSerials);
})
.onFailure(error -> {
LOG.debug("startTest() Failed: ", error);
})
.onSuccess(server -> {
LOG.debug("Finished startTest!!");
LOG.debug(server.encodePrettily());
});
}
You can create a context object the has setters/getter for the data that is passed around in the Futures.
Compose guareantees serial execution of your Futures, you can set the results and assume that the result is present in your context object in the next compose section. Example:
public Future<JsonArray> startTest(int jobID, RoutingContext context) {
MyTestContext myTestCtx = new MyTestConext();
return jobDao.getJob(jobID, context)
.compose(job -> {
myTestCtx.setjob(job);
return productDao.getLabelParameters(job, context);
})
.compose(labelParameters -> {
myTestCtx.setLabelParameters(labelparameters);
return jobDao.getJobParentChildForPrint(jobID, context, true, labelParameters);
})
.compose(parentChildSerials -> {
var labelParameters = myTestCtx.getLabelParameters();
prepareSerialForPrint(parentChildSerials, labelParameters); //Pass Here
return Future.succeededFuture(parentChildSerials);
})
.onSuccess(server -> LOG.debug("Finished startTest!!"));
}
You can also make use of the java records for this.
Alternatively you can nest the compose parts as follows:
public Future<JsonArray> startTest(int jobID, RoutingContext context) {
return jobDao.getJob(jobID, context)
.compose(job -> productDao.getLabelParameters(job, context))
.compose(labelParameters -> {
return jobDao.getJobParentChildForPrint(jobID, context, true, labelParameters)
.compose(parentChildSerials -> {
LOG.debug(" Future Returned Job Parent-Child to Print: ");
prepareSerialForPrint(parentChildSerials, labelParameters); //Pass Here
return Future.succeededFuture(parentChildSerials);
})
})
.onFailure(error -> LOG.debug("startTest() Failed: ", error))
.onSuccess(server -> LOG.debug(server.encodePrettily()));
}
Related
What I am trying to accomplish is to return a simple Mono Response.
I am calling different backends API's in the method detailsHandler.fetchDetailsValue
Since this is a Synchronous blocking call, I am wrapping it in Mono.fromCallable as suggested in the documentation.
But I am facing this error upon compiling -
error: local variables referenced from a lambda expression must be final or effectively final
Actually, inside .subscribe lambda I am trying to assign to Response object which is declared outside the lambda. Since I need to assign the object returned from the fetchDetailsValue method upon subscription, how can I return this response object ?
Please correct me if wrong below and suggest how to fix this. Appreciate any inputs. Thanks!
Below is the sample code -
#Override
public Mono<Response> getDetails(Mono<RequestDO> requestDO) {
return requestDO.flatMap(
request -> {
Response response = new Response();
Mono<List<Object>> optionalMono = Mono.fromCallable(() -> {
return detailsHandler.fetchDetailsValue(request);
});
optionalMono. subscribeOn(Schedulers.boundedElastic())
.subscribe(result -> {
Cat1 cat1Object = null;
Cat2 cat2Object = null;
for(Object obj : result) {
if (obj instanceof Cat1) {
cat1Object = (Cat1) obj;
response.addResponseObj(cat1Object); // error: local variables referenced from a lambda expression must be final or effectively final
}
if (obj instanceof Cat2) {
cat2Object = (Cat2) obj;
response.addResponseObj(cat2Object); // error: local variables referenced from a lambda expression must be final or effectively final
}
}
});
return Mono.just(response);
});
}
When I tried to declare that Response object inside subscribe method and tried to return as and when value is received. But getting the error - Void methods cannot return a value
Below is the code -
#Override
public Mono<Response> getDetails(Mono<RequestDO> requestDO) {
return requestDO.flatMap(
request -> {
Mono<List<Object>> optionalMono = Mono.fromCallable(() -> {
return detailsHandler.fetchDetailsValue(request);
});
optionalMono. subscribeOn(Schedulers.boundedElastic())
.subscribe(result -> {
Response response = new Response(); // Added this inside subscribe lambda. But now getting - Void methods cannot return a value
Cat1 cat1Object = null;
Cat2 cat2Object = null;
for(Object obj : result) {
if (obj instanceof Cat1) {
cat1Object = (Cat1) obj;
response.addResponseObj(cat1Object);
}
if (obj instanceof Cat2) {
cat2Object = (Cat2) obj;
response.addResponseObj(cat2Object);
}
}
return Mono.just(response); // Added this inside subscribe lambda. But now getting - Void methods cannot return a value
});
});
}
UPDATE:
When I tried like below, I am getting errors. Please correct if anything I am doing wrong.
public Mono<Response> getDetails(Mono<RequestDO> requestDO) {
return requestDO
.flatMap(request -> Mono.fromCallable(() -> detailsHandler.fetchDetailsValue(request)))
.map(result -> {
Response response = new Response();
for (Object obj : result) {
if (obj instanceof Cat1) {
response.addResponseObj((Cat1) obj);
}
if (obj instanceof Cat2) {
response.addResponseObj((Cat2) obj);
}
}
return response;
})
.map(result1 -> {
Response response = resultnew;
requestDO.flatMap(request -> Mono.fromCallable(() -> detailsHandler.fetchAdditionalValue(request, response)))
.map(result2 -> {
return result2;
});
}
You should not call subscribe inside your Reactor pipeline. Subscribe should be considered a terminal operation that starts the pipeline asynchronously in an unknown time in the future, and should only serve to connect to some other part of your system.
What you want is to transform your List<Object> into a new Response using a simple synchronous function, the map operator is made for this:
public Mono<Response> getDetails(Mono<RequestDO> requestDO) {
return requestDO
.flatMap(request -> Mono.fromCallable(() -> detailsHandler.fetchDetailsValue(request)))
.map(result -> {
Response response = new Response();
for (Object obj : result) {
if (obj instanceof Cat1) {
response.addResponseObj((Cat1) obj);
}
if (obj instanceof Cat2) {
response.addResponseObj((Cat2) obj);
}
}
return response;
});
}
Update
For your updated question you want to use both request and response to call another Mono. You can do this by first pulling the map inside the flatMap, then add another flatMap to it:
public Mono<Response> getDetails(Mono<RequestDO> requestDO) {
return requestDO
.flatMap(request -> Mono.fromCallable(() -> detailsHandler.fetchDetailsValue(request))
.map(result -> {
Response response = new Response();
for (Object obj : result) {
if (obj instanceof Cat1) {
response.addResponseObj((Cat1) obj);
}
if (obj instanceof Cat2) {
response.addResponseObj((Cat2) obj);
}
}
return response;
})
.flatMap(response -> Mono.fromCallable(() -> detailsHandler.fetchAdditionalValue(request, response))));
}
i use spring boot's RedisTemplate with scala, and i write this code:
redisTemplate1.executePipelined(new RedisCallback[String] {
override def doInRedis(connection: RedisConnection): String = {
MyCode......
null
}
}, redisTemplate1.getValueSerializer)
usually, it's can be wrote like this:
redisTemplate1.executePipelined((connection: RedisConnection) => {
MyCode......
null
}, redisTemplate1.getValueSerializer)
and this style is running well in java:
redisTemplate1.executePipelined((RedisConnection conn) -> {
MyCode......
return null;
}, redisTemplate1.getValueSerializer());
but when i compile in this style with scala, i get an error, so why this happend and how can i use single abstract method in this case?
overloaded method value executePipelined with alternatives:
(x$1: org.springframework.data.redis.core.RedisCallback[_],x$2: org.springframework.data.redis.serializer.RedisSerializer[_])java.util.List[Object] <and>
(x$1: org.springframework.data.redis.core.SessionCallback[_],x$2: org.springframework.data.redis.serializer.RedisSerializer[_])java.util.List[Object]
cannot be applied to (org.springframework.data.redis.connection.RedisConnection => Null, org.springframework.data.redis.serializer.RedisSerializer[?0(in method syncSegmentSrc)])
redisTemplate1.executePipelined((connection: RedisConnection) => {
the executePipelined function source code like this:
#Override
public List<Object> executePipelined(SessionCallback<?> session, #Nullable RedisSerializer<?> resultSerializer) {
Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
Assert.notNull(session, "Callback object must not be null");
RedisConnectionFactory factory = getRequiredConnectionFactory();
// bind connection
RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
try {
return execute((RedisCallback<List<Object>>) connection -> {
connection.openPipeline();
boolean pipelinedClosed = false;
try {
Object result = executeSession(session);
if (result != null) {
throw new InvalidDataAccessApiUsageException(
"Callback cannot return a non-null value as it gets overwritten by the pipeline");
}
List<Object> closePipeline = connection.closePipeline();
pipelinedClosed = true;
return deserializeMixedResults(closePipeline, resultSerializer, hashKeySerializer, hashValueSerializer);
} finally {
if (!pipelinedClosed) {
connection.closePipeline();
}
}
});
} finally {
RedisConnectionUtils.unbindConnection(factory);
}
}
#Override
public List<Object> executePipelined(RedisCallback<?> action, #Nullable RedisSerializer<?> resultSerializer) {
return execute((RedisCallback<List<Object>>) connection -> {
connection.openPipeline();
boolean pipelinedClosed = false;
try {
Object result = action.doInRedis(connection);
if (result != null) {
throw new InvalidDataAccessApiUsageException(
"Callback cannot return a non-null value as it gets overwritten by the pipeline");
}
List<Object> closePipeline = connection.closePipeline();
pipelinedClosed = true;
return deserializeMixedResults(closePipeline, resultSerializer, hashKeySerializer, hashValueSerializer);
} finally {
if (!pipelinedClosed) {
connection.closePipeline();
}
}
});
}
In cases like this, it should help to specify the type explicitly:
redisTemplate1.executePipelined({ connection =>
YourCode…
}: RedisCallback[String], redisTemplate1.getValueSerializer)
Note the type ascription : RedisCallback[String].
I cannot get the OnComplete() method to be called after all items are processed. I need to do so in order to (al the very least) hide the loading view. I'm a little new to JavaRX so I don't know where exactly is the problem. Can you help me to get the OnComplete() called when all items are processed?
The code does the following:
Show the loading view and get the list of items (just references).
Check if they are local or remote items.
If they are local, get them and add them to the list.
If they are remote, download them and add them to the list.
With the list built, draw the data on the UI.
Final processing and hiding of the loading view.
The code is the following:
private void loadDataRX(final long fromTime, final long toTime) {
mLoadingPb.setVisibility(View.VISIBLE);
iCompositeDisposable.clear();
iCompositeDisposable.add(mViewModel.getItems(fromTime, toTime)
.subscribeOn(Schedulers.io())
.flatMap(items -> {
Activity context = ItemFragment.this.getActivity();
if (context == null) {
Log.e(TAG, "Cannot present results: context is null");
return Flowable.empty();
} else {
context.runOnUiThread(() -> {
mItems.clear();
mCustomView.reset();
});
if (items != null && items.size() > 0) {
return Flowable.just(items);
} else {
Log.i(TAG, "No items.");
return Flowable.just(Collections.singletonList(new Item(-1))); // This is my current way of solving a similar problem so as to know if I don't have any items
}
}
})
.concatMapIterable(items -> items)
.concatMap(item -> {
if (item.getUid() == -1) {
return Flowable.just(item);
}
String file = item.getFileName();
boolean uploaded = item.isUploaded();
if (uploaded) { // Remote file
if (item.getUid() > 0) {
return iRetrofit.create(RestApi.class).getItem(item.getUid());
} else {
return Flowable.empty();
}
} else { // Local file
return Flowable.just(item);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(item -> {
Log.i(TAG, "Loaded items RX");
if (item instanceof Item) {
//Do stuff with the item and the files
} else if (item instanceof ResponseBody) {
//This is dirty but I didn't find another way. So here I basically extract the items and the files from the server's response. At least, it works.
} else {
Log.i(TAG, "No results for the given dates");
}
}, throwable -> {
mLoadingPb.setVisibility(View.GONE);
Log.e(TAG, "Error: " + throwable.getMessage());
}, () -> {
mLoadingPb.setVisibility(View.GONE);
Log.i(TAG, "Loading results completed"); // Can't get this to be called
})
);
}
Thanks in advance.
I guess that mViewModel.getItems returns Flowable. For flowable to complete we need to explicitly dispose it.
To resolve that you can make mViewModel.getItems to return Single<List<ItemType>>, then transform stream using .flatMapObservable { Observable.fromIterable(it) } to process each item.
I have following snippet in ViewModel.
public ReactiveCommand<object, System.Reactive.Unit> LoadCustomerDetails;
ReactiveCommand<OrderViewPager<SalesOrderOrderOptionsEnum>, CommandSubmitResultDto<List<SalesOrderDto>>> _loadSalesOrderList;
public ReactiveCommand<OrderViewPager<SalesOrderOrderOptionsEnum>, CommandSubmitResultDto<List<SalesOrderDto>>> LoadSalesOrderList
{
get { return _loadSalesOrderList; }
private set { this.RaiseAndSetIfChanged(ref _loadSalesOrderList, value); }
}
this.LoadSalesOrderList = ReactiveCommand.CreateFromTask<Pager<OrderOptionsEnum>, CommandSubmitResultDto<List<SalesOrderDto>>>(
async filter =>
{
Debug.WriteLine("Load SalesOrderList...");
Debug.WriteLine("Customer Id : " + SelectedCustomerId);
await LoadCustomerDetails.Execute();
var result = await SalesOrderMobApi.GetByCustomerTraderEntityIdPaged(SelectedCustomerId, filter, null, SalesOrderTypeEnum.SalesOrder, SalesOrderPOOptions.NotOriginatingFromPurchaseOrder);
return result;
})
.DisposeWith(ViewModelBindings.Value);
this.LoadSalesOrderList.ThrownExceptions
.Subscribe(ex =>
{
Debug.WriteLine("Load SalesOrderList Failed!");
});
this.LoadSalesOrderList
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(result =>
{
if (result.PagingInfo.CurrentPage > 1)
{
foreach (var item in result.Data)
{
SalesOrdersList.Add(SalesOrderVMM.From(item));
}
}
else
{
SalesOrdersList.Clear();
foreach (var item in result.Data)
{
SalesOrdersList.Add(SalesOrderVMM.From(item));
}
}
});
LoadCustomerDetails = ReactiveCommand.CreateFromTask<object, System.Reactive.Unit>(
async _ =>
{
Debug.WriteLine(SelectedCustomerId);
var customers = await TraderEntityMobApi.GetById(SelectedCustomerId);
var customer = customers.Data;
SelectedCustomer = customer;
return System.Reactive.Unit.Default;
}
).DisposeWith(ViewModelBindings.Value);
It sometimes gives exception as follows.
System.NullReferenceException: Object reference not set to an instance of an object.
06-20 16:05:02.480 I/MonoDroid(15304): at DistributrIII.Mobile.Lib.VM.SalesOrder.CreateSOListVM.<RegisterObservables>b__43_2 (System.Collections.Generic.List`1[T] result) [0x0000e] in C:\Users\gayanbu\Source\Repos\Distributr 3.0 UI\Mobile\DistributrIII.Mobile.Lib\VM\SalesOrder\CreateSOListVM.cs:131 .at System.Reactive.AnonymousSafeObserver`1[T].OnNext (T value) [0x0000a] in <99f8205c51c44bb480747b577b8001ff>:0
06-20 16:05:02.480 I/MonoDroid(15304): at System.Reactive.ScheduledObserver`1[T].Run (System.Object state, System.Action`1[T] recurse) [0x000f5] in <99f8205c51c44bb480747b577b8001ff>:0
06-20 16:05:02.480 I/MonoDroid(15304): at System.Reactive.Concurrency.Scheduler+<>c__DisplayClass49_0`1[TState].<InvokeRec1>b__0 (TState state1) [0x0001e] in <99f8205c51c44bb480747b577b8001ff>:0
06-20 16:05:02.480 I/MonoDroid(15304): at System.Reactive.Concurrency.Scheduler.InvokeRec1[TState] (System.Reactive.Concurrency.IScheduler scheduler,
I guess it tries to execute the code inside reactive command ,LoadSalesOrderList even the result of this is null. How to handle this ? Could someone kindly explain the proper way of subscribing to Reactive Command. I am executing this command in the page load as, this.ViewModel.LoadSalesOrderList.Execute().subscribe(new Pager<OrderOptionsEnum>())
Thanks!
if you want your command when throw exception to be catched in ThrownExceptions execute command like Observable.Return(input).InvokeCommand(Command).DisposeWith(disposable) where input is the input for command and Command is the name of the Command
I have this method deleteFeedTable() which returns a Completable and when it finishes I want to start another Disposable.
What I did is combine the two using operator concatWith, but this results in a nested subscription and I'd like to avoid that.
disposables.add(
localDataSource.deleteFeedTable()
.doOnComplete(() -> { preferencesManager.setFeedTableUpdateState(false);
})
.concatWith(new Completable() {
#Override
protected void subscribeActual(CompletableObserver s) {
s.onSubscribe(localDataSource.getLastStoredId()
.flatMap(lastStoredId -> remoteDataSource.getFeed(lastStoredId))
.doOnNext(feedItemList -> localDataSource.saveFeed(feedItemList))
.map(feedItemList -> {
Timber.i("MESA STO MAP");
List<Feed> feedList = new ArrayList<>();
for (FeedItem feedItem : feedItemList) {
feedList.add(mapper.from(feedItem));
}
downloadImageUseCase.downloadPhotos(feedList);
return feedList;
})
.subscribe());
}
})
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.mainThread())
.subscribe(() -> {}, throwable -> Log.i("THROW", "loadData ", throwable)));
Is there a way I can avoid the nested subscription ? Or is there another way to add it to the disposables variable so I can clear the subscription later ?
Use andThen:
disposables.add(
localDataSource.deleteFeedTable()
.doOnComplete(() -> {
preferencesManager.setFeedTableUpdateState(false);
})
.andThen(
localDataSource.getLastStoredId()
.flatMap(lastStoredId -> remoteDataSource.getFeed(lastStoredId))
.doOnNext(feedItemList -> localDataSource.saveFeed(feedItemList))
.map(feedItemList -> {
Timber.i("MESA STO MAP");
List<Feed> feedList = new ArrayList<>();
for (FeedItem feedItem : feedItemList) {
feedList.add(mapper.from(feedItem));
}
downloadImageUseCase.downloadPhotos(feedList);
return feedList;
})
)
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.mainThread())
.subscribe(() -> {}, throwable -> Log.i("THROW", "loadData ", throwable)));