I'm a little bit new to RxJava. I am trying to emit another item if onError() get called without losing the error(I still want onError() to be called on the observer). but when I'm implementing each of the error handling methods declared in the docs the error being swallowed and on error isn't being called. any solutions?
edit:
that's what I've tried to do yesterday -
#Override
public Observable<ArrayList<Address>> getAirports() {
return new Observable<ArrayList<AirportPOJO>>() {
#Override
protected void subscribeActual(Observer<? super ArrayList<AirportPOJO>> observer) {
try {
// get airports from api list and map it
ArrayList<AirportPOJO> airportsList = apiDb.getAirportsList(POJOHelper.toPOJO(AppCredentialManager.getCredentials()));
observer.onNext(airportsList);
} catch (Exception e) {
e.printStackTrace();
observer.onError(handleException(e));
}
}
}.map(AirportsMappers.getAirportsPojoToDomainAirportsMapper()).doOnNext(new Consumer<ArrayList<Address>>() {
#Override
public void accept(ArrayList<Address> airportsList) throws Exception {
// if airports loaded from api - save them to local db
if (airportsList != null) {
try {
localDb.saveAirportList(AirportsMappers.getAirportsToLocalDbAirportsMapper().apply(airportsList));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).onErrorResumeNext(new Function<Throwable, ObservableSource<? extends ArrayList<Address>>>() {
#Override
public ObservableSource<? extends ArrayList<Address>> apply(final Throwable throwable) throws Exception {
// load the local airports -
ArrayList<LocalDbAirportEntity> localAirportsEntities = localDb.getAirports();
// map
ArrayList<Address> airports = AirportsMappers.getLocalDbAirportsToAirportsMapper().apply(localAirportsEntities);
// return the concat observable with the error
return Observable.just(airports).concatWith(Observable.
<ArrayList<Address>>error(new Callable<Throwable>() {
#Override
public Throwable call() throws Exception {
return throwable;
}
}));
}
});
}
today I tought I might doing it wrong and tried -
#Override
public Observable<ArrayList<Address>> getAirports() {
ArrayList<Observable<ArrayList<Address>>> observables = new ArrayList<>();
observables.add(apiDb.getAirportsList(POJOHelper.toPOJO(AppCredentialManager.getCredentials())).map(AirportsMappers.getAirportsPojoToDomainAirportsMapper()));
observables.add(localDb.getAirports().map(AirportsMappers.getLocalDbAirportsToAirportsMapper()));
Observable<ArrayList<Address>> concatenatedObservable = Observable.concatDelayError(observables);
return concatenatedObservable;
}
but I've got the same result. the onNext() called with the data of the second observable and the onError() not being called afterwards.
Resume with the desired value concatenated with the original error:
source.onErrorResumeNext(error ->
Observable.just(item).concatWith(Observable.<ItemType>error(error))
);
Related
I'm using the packages:
// Apollo
implementation "com.apollographql.apollo:apollo-runtime:2.4.1"
implementation "com.apollographql.apollo:apollo-rx3-support:2.4.1"
// RxJava
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
implementation 'io.reactivex.rxjava3:rxjava:3.0.4'
And I call function:
public static Flowable<Response<CallSubscription.Data>> callSubscription(String room) {
ApolloSubscriptionCall<CallSubscription.Data> call = getApolloClient()
.subscribe(new CallSubscription(room));
return Rx3Apollo.from(call)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext((dataResponse -> {
System.out.println("datat" + dataResponse);
if (dataResponse.getErrors() != null) {
throw new ApolloException(dataResponse.getErrors().get(0).getMessage());
}
}))
.filter((dataResponse -> dataResponse.getData() != null));
}
which is used here:
NetworkService.callSubscription(room).subscribeWith(new DisposableSubscriber<Response<CallSubscription.Data>>() {
#Override
protected void onStart() {
super.onStart();
System.out.println("onStart");
}
#Override
public void onNext(#NonNull Response<CallSubscription.Data> dataResponse) {
System.out.println("onNext " + dataResponse);
}
#Override
public void onError(#NonNull Throwable e) {
System.out.println("onError " + e);
}
#Override
public void onComplete() {
System.out.println("onComplete ");
}
});
And I need to handle exception from NodeJs inside subscription resolver, which is throw new NotFoundException('SOME_ERROR');, but I got just "onStart", nothing else (no "onNext", "onError", "onComplete"). While playground works fine and I'm handling exception there, and query and mutations also work fine with exceptions. What happened with Rx3Apollo subscriptions? it's my mistake or this package has bug?
I need to migrate to Kinesis library to version 2.2.11 so I followed the tutorial: https://docs.aws.amazon.com/streams/latest/dev/kcl-migration.html
I need to run multiple instances of my consumer app, so every one of them needs to have an unique application name in order to have a separate lease table in DynamoDb.
When initializing the consumer Kinesis runs DynamoDBLeaseRefresher.createLeaseTableIfNotExists which checks if a new table needs to be created for this application name and creates one if it cannot be found.
So 2 operations are performed:
DescribeTable - it returns the table info or throws a ResourceNotFoundExecption,
if needed - CreateTable.
The problem for me is with the DescribeTable method. When I am looking for an existing table it returns it with no problem. But when I am looking for a non-existent table it throws the ResourceNotFoundExecption -> so far so good. Unfortunately it then gets wrapped and is now:
java.util.concurrent.CompletionException: software.amazon.awssdk.core.exception.SdkClientException: Unable to execute HTTP request: software.amazon.awssdk.awscore.exception.AwsServiceException$Builder.extendedRequestId(Ljava/lang/String;)Lsoftware/amazon/awssdk/awscore/exception/AwsServiceException$Builder;
and the app expecting ResourceNotFoundException gets something different instead and crashes.
The wrapped exception message is a bit misleading: "Unable to execute HTTP request" since the request was performed and returned the proper message: "Resource not found".
Funny thing is that it sometimes works, the exception does not get wrapped, the CreateTable operation is performed and the consumer starts properly.
I have made a workaround for it for now where I just create the table before the initialization of the LeaseCoordinator, so it always gets the existing table.
here is my code:
public KinesisStreamReaderService(String streamName, String applicationName, String regionName) {
KinesisAsyncClient kinesisClient = KinesisAsyncClient.builder()
.credentialsProvider(EnvironmentVariableCredentialsProvider.create())
.region(Region.of(connectionProperties.getRegion()))
.httpClientBuilder(createHttpClientBuilder())
.build();
DynamoDbAsyncClient dynamoClient = DynamoDbAsyncClient.builder().region(Region.of(regionName)).build();
CloudWatchAsyncClient cloudWatchClient = CloudWatchAsyncClient.builder().region(Region.of(regionName)).build();
// if(!dynamoDbTableExists(dynamoClient, applicationName)) {
// createDynamoDbTable(dynamoClient, applicationName);
// }
ConfigsBuilder configsBuilder = new ConfigsBuilder(streamName, applicationName, kinesisClient,
dynamoClient, cloudWatchClient, workerId(), KinesisReaderProcessor::new);
configsBuilder.retrievalConfig().initialPositionInStreamExtended(
InitialPositionInStreamExtended.newInitialPosition(
InitialPositionInStream.LATEST));
scheduler = new Scheduler(
configsBuilder.checkpointConfig(),
configsBuilder.coordinatorConfig(),
configsBuilder.leaseManagementConfig(),
configsBuilder.lifecycleConfig(),
configsBuilder.metricsConfig(),
configsBuilder.processorConfig(),
configsBuilder.retrievalConfig().retrievalSpecificConfig(new PollingConfig(streamName, kinesisClient))
);
}
private void createDynamoDbTable(DynamoDbAsyncClient dynamoClient, String applicationName) {
log.info("Creating new lease table: {}", applicationName);
CompletableFuture<CreateTableResponse> createTableFuture = dynamoClient
.createTable(CreateTableRequest.builder()
.provisionedThroughput(ProvisionedThroughput.builder().readCapacityUnits(10L).writeCapacityUnits(10L).build())
.tableName(applicationName)
.keySchema(KeySchemaElement.builder().attributeName("leaseKey").keyType(KeyType.HASH).build())
.attributeDefinitions(AttributeDefinition.builder().attributeName("leaseKey").attributeType(
ScalarAttributeType.S).build())
.build());
try {
CreateTableResponse createTableResponse = createTableFuture.get();
log.debug("Created new lease table: {}", createTableResponse.tableDescription().tableName());
} catch (InterruptedException | ExecutionException e) {
throw new DataStreamException(e.getMessage(), e);
}
}
private boolean dynamoDbTableExists(DynamoDbAsyncClient dynamoClient, String tableName) {
CompletableFuture<DescribeTableResponse> describeTableResponseCompletableFutureNew = dynamoClient
.describeTable(DescribeTableRequest.builder()
.tableName(tableName).build());
try {
DescribeTableResponse describeTableResponseNew = describeTableResponseCompletableFutureNew
.get();
return nonNull(describeTableResponseNew);
} catch (InterruptedException | ExecutionException e) {
log.info(e.getMessage(), e);
}
return false;
}
private static String workerId() {
String workerId;
try {
workerId = format("%s_%s", getLocalHost().getCanonicalHostName(), randomUUID().toString());
} catch (UnknownHostException e) {
workerId = randomUUID().toString();
}
return workerId;
}
#Override
public void read(Consumer<String> consumer) {
this.consumer = consumer;
scheduler.run();
}
private class KinesisReaderProcessor implements ShardRecordProcessor {
private String shardId;
#Override
public void initialize(InitializationInput initializationInput) {
this.shardId = initializationInput.shardId();
log.info("Initializing record processor for shard: {}", shardId);
}
#Override
public void processRecords(ProcessRecordsInput processRecordsInput) {
log.debug("Checking shard {} for new records", shardId);
List<KinesisClientRecord> records = processRecordsInput.records();
if (!records.isEmpty()) {
log.debug("Processing {} records from kinesis stream shard {}", records.size(), shardId);
records.forEach(record -> {
String json = UTF_8.decode(record.data()).toString();
log.info(json);
consumer.accept(json);
});
}
}
#Override
public void leaseLost(LeaseLostInput leaseLostInput) {
log.info("Record processor has lost lease, terminating");
}
#Override
public void shardEnded(ShardEndedInput shardEndedInput) {
try {
shardEndedInput.checkpointer().checkpoint();
} catch (ShutdownException | InvalidStateException e) {
log.error(e.getMessage(), e);
}
}
#Override
public void shutdownRequested(ShutdownRequestedInput shutdownRequestedInput) {
try {
shutdownRequestedInput.checkpointer().checkpoint();
} catch (ShutdownException | InvalidStateException e) {
log.error(e.getMessage(), e);
}
}
}
}
Am I missing some configuration for the scheduler or something? Why is it sometimes working?
Thanks
Edit:
The problem is this block of code in DynamoDBLeaseRefresher.tableStatus() is invoked to check if the table exists:
DescribeTableResponse result;
try {
try {
result =
(DescribeTableResponse)FutureUtils.resolveOrCancelFuture(this.dynamoDBClient.describeTable(request), this.dynamoDbRequestTimeout);
} catch (ExecutionException var5) {
throw exceptionManager.apply(var5.getCause());
} catch (InterruptedException var6) {
throw new DependencyException(var6);
}
} catch (ResourceNotFoundException var7) {
log.debug("Got ResourceNotFoundException for table {} in leaseTableExists, returning false.", this.table);
return null;
}
and in my case it should get ResourceNotFoundException if the table is not found, but as I said the expection gets wrapped to CompletionException before it reaches the appropriate catch block and is caught in the code here:
catch (ExecutionException var5) {
throw exceptionManager.apply(var5.getCause());
This is happening 20 times in the loop while trying to Initialize the LeaseCoordinator and then just stops trying to initialize the connection. (As mentioned above it works occasionally, but that makes it even stranger to me)
With my workaround it only needs 1 try to get initialized
You don't need to create a lease table manually - DynamoDBLeaseCoordinator will create one if not exists on initialization and wait until it exists:
#Override
public void initialize() throws ProvisionedThroughputException, DependencyException, IllegalStateException {
final boolean newTableCreated =
leaseRefresher.createLeaseTableIfNotExists(initialLeaseTableReadCapacity, initialLeaseTableWriteCapacity);
if (newTableCreated) {
log.info("Created new lease table for coordinator with initial read capacity of {} and write capacity of {}.",
initialLeaseTableReadCapacity, initialLeaseTableWriteCapacity);
}
// Need to wait for table in active state.
final long secondsBetweenPolls = 10L;
final long timeoutSeconds = 600L;
final boolean isTableActive = leaseRefresher.waitUntilLeaseTableExists(secondsBetweenPolls, timeoutSeconds);
if (!isTableActive) {
throw new DependencyException(new IllegalStateException("Creating table timeout"));
}
}
The issue in your case, I think, is that it's eventually created and you probably should periodically check until table appears - like DynamoDBLeaseCoordinator#initialize() does.
I would like to annotate some of my test cases with KnownFault - which would do pretty much what expectedException does plus some magic using YouTrack's REST API. I would also like to have an IntermittentFailure attribute which would mean that I'm aware that the test might occasionally fail with [exception] [message] but I wouldn't want this to block the rest of my build chain.
After some research I found that my test class should implement IHookable, then I could have something like this:
#Override
public void run(IHookCallBack callBack, ITestResult result) {
callBack.runTestMethod(result);
if (result.getThrowable().getCause() instanceof IllegalArgumentException){
System.out.println("This is expected.");
result.setThrowable(null);
}
else{
System.out.println("Unexpected exception");
}
}
The problem with this is the actual implementation of invokeHookable:
final Throwable[] error = new Throwable[1];
IHookCallBack callback = new IHookCallBack() {
#Override
public void runTestMethod(ITestResult tr) {
try {
invokeMethod(thisMethod, testInstance, parameters);
} catch (Throwable t) {
error[0] = t;
tr.setThrowable(t); // make Throwable available to IHookable
}
}
#Override
public Object[] getParameters() {
return parameters;
}
};
hookable.run(callback, testResult);
if (error[0] != null) {
throw error[0];
}
Unfortunately that last line means that my test case is going to throw an exception no matter what as the error array is completely out of my hands in the run method.
So, what would be the proper way of intercepting an exception and handling it the way I want to?
What you are trying to do is really interesting. You should try to propose changes on https://github.com/cbeust/testng/pull/
But maybe IHookable is not the best listener you can use. Did you try IInvokedMethodListener?
void afterInvocation(IInvokedMethod method, ITestResult result) {
if (result.getThrowable().getCause() instanceof IllegalArgumentException) {
System.out.println("This is expected.");
result.setThrowable(null);
result.setStatus(SUCCESS); // If you want to change the status
} else {
System.out.println("Unexpected exception");
}
}
I have an hour of experience using RxJava and I am trying to implement it in my project instead of using interfaces and listeners.
I have an async task which calls a google cloud endpoint method in a separate module and receives a List<Profile> when done.
In the onPostExecute() method of the async task, I call onNext so that any subscribers receive this data.
Here is what the AsyncTask looks like:
private BirthpayApi mApi;
private String mUserId;
private ReplaySubject<List<Profile>> notifier = ReplaySubject.create();
public GetFriends(String userId) {
mUserId = userId;
}
public Observable<List<Profile>> asObservable() {
return notifier;
}
#Override
protected List<Profile> doInBackground(Void... params) {
if (mApi == null) {
BirthpayApi.Builder builder = new BirthpayApi.Builder(AndroidHttp.newCompatibleTransport(),
new AndroidJsonFactory(), null)
// options for running against local devappserver
// - 10.0.2.2 is localhost's IP address in Android emulator
// - turn off compression when running against local devappserver
.setRootUrl("http://10.0.2.2:8080/_ah/api/")
.setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() {
#Override
public void initialize(AbstractGoogleClientRequest<?> abstractGoogleClientRequest) throws IOException {
abstractGoogleClientRequest.setDisableGZipContent(true);
}
});
mApi = builder.build();
}
try {
return mApi.getFriends(mUserId).execute().getItems();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
#Override
protected void onPostExecute(List<Profile> friends) {
super.onPostExecute(friends);
notifier.onNext(friends);
}
In my Fragment I then want to collect this data from the async task calling the onNext() method. I therefore use implements Action1<List<Profile>> when declaring my class which also extends Fragment.
In the onCall() method that comes from the Action1 interface I collect the data sent from the Async task:
#Override
public void call(List<Profile> profiles) {
if (profiles.size() > 0) {
updateAdapter(profiles);
} else
setUpNoFriendsViews();
}
I am following along with treehouse but they use a object to model their data which becomes the observable instead of using an async class and they use an adapter as the observer. Am I doing this wrong, either way how do I get it to work?
It doesn't look like you're subscribing to the Observable you're creating anywhere and it's not clear where you're calling execute on the AsyncTask. Also I don't think you'd want a ReplaySubject but that depends on what you're trying to achieve.
All that aside I'd suggest completely switching over to Rx rather than mixing up Rx and AsyncTasks. In this case in your model class make a method something like:
public Observable<List<Profile>> getProfiles() {
return Observable.defer(new Func0<Observable<List<Profile>>>() {
#Override
public Observable<List<Profile>> call() {
if (mApi == null) {
BirthpayApi.Builder builder = new BirthpayApi.Builder(AndroidHttp.newCompatibleTransport(),
new AndroidJsonFactory(), null)
// options for running against local devappserver
// - 10.0.2.2 is localhost's IP address in Android emulator
// - turn off compression when running against local devappserver
.setRootUrl("http://10.0.2.2:8080/_ah/api/")
.setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() {
#Override
public void initialize(AbstractGoogleClientRequest<?> abstractGoogleClientRequest) throws IOException {
abstractGoogleClientRequest.setDisableGZipContent(true);
}
});
mApi = builder.build();
}
try {
List<Profile> profiles = mApi.getFriends(mUserId).execute().getItems();
return Observable.just(profiles);
} catch (IOException e) {
return Observable.error(e);
}
}
});
}
Then in your Fragment:
modal.getProfiles()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new Action1<List<Profile>>() {
//...
},
new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
throwable.printStackTrace();
}
}
);
I've implemented Gdxpay into my libgdx game but when I call requestPurchase(), nothing happens. I followed this tutorial https://github.com/libgdx/gdx-pay/wiki/Integration-example-with-resolvers but I'm not sure where I'm going wrong.
Here is the main game class where the purchase observer is:
public MyGame extends Application adapter {
public MyGame(IActivityRequestHandler handler) {
// TODO Auto-generated constructor stub
super();
myRequestHandler = handler;
// ---- IAP: define products ---------------------
purchaseManagerConfig = new PurchaseManagerConfig();
purchaseManagerConfig.addOffer(new Offer().setType(OfferType.ENTITLEMENT).setIdentifier(SKU_REMOVE_ADS));
}
public PurchaseObserver purchaseObserver = new PurchaseObserver() {
#Override
public void handleRestore (Transaction[] transactions) {
for (int i = 0; i < transactions.length; i++) {
if (checkTransaction(transactions[i].getIdentifier()) == true) break;
}
// to make a purchase (results are reported to the observer)
PurchaseSystem.purchase(SKU_REMOVE_ADS);
}
#Override
public void handleRestoreError (Throwable e) {
// getPlatformResolver().showToast("PurchaseObserver: handleRestoreError!");
Gdx.app.log("ERROR", "PurchaseObserver: handleRestoreError!: " + e.getMessage());
throw new GdxRuntimeException(e);
}
#Override
public void handleInstall () {
// getPlatformResolver().showToast("PurchaseObserver: installed successfully...");
Gdx.app.log("handleInstall: ", "successfully..");
}
#Override
public void handleInstallError (Throwable e) {
//getPlatformResolver().showToast("PurchaseObserver: handleInstallError!");
Gdx.app.log("ERROR", "PurchaseObserver: handleInstallError!: " + e.getMessage());
throw new GdxRuntimeException(e);
}
#Override
public void handlePurchase (Transaction transaction) {
checkTransaction(transaction.getIdentifier());
}
#Override
public void handlePurchaseError (Throwable e) {
if (e.getMessage().equals("There has been a Problem with your Internet connection. Please try again later")) {
// this check is needed because user-cancel is a handlePurchaseError too)
// getPlatformResolver().showToast("handlePurchaseError: " + e.getMessage());
}
throw new GdxRuntimeException(e);
}
#Override
public void handlePurchaseCanceled () {
}
};
protected boolean checkTransaction (String ID) {
boolean returnbool = false;
if (SKU_REMOVE_ADS.equals(ID)) {
myRequestHandler.showAds(false);
returnbool = true;
}
return returnbool;
}
public void create() {
...
Here is where requestPurchase is called:
public class MainMenu extends Screen {
#Override
public void update() {
...
if (removeBounds.contains(touchPoint.x, touchPoint.y)) {
MyGame.getPlatformResolver().requestPurchase(MyGame.SKU_REMOVE_ADS);
}
}
...
}
Many thanks.
Edit: Ok logcat says the following error when I request a purchase:
5188-5220/com.comp.myGame.android I/ERRORīš gdx-pay: requestPurchase(): purchaseManager == null
So that means pruchaseManager is null, but according to the tutorial in this instance it should cause the correct purchaseManager to be called so I'm still confused...
I had exactly the same issue. I followed the tutorial as well, but changed the distributed resolver system to a more local defined system where all app store keys are set in the main game class.
This didn't work (with the same error you got). I then re-engineered the code to follow exactly the tutorial - with all the resolver bells and whistles. Next, I got a "no suitable app store found" error while creating the purchaseManager (at this point, I celebrated because it at least TRIED to create it).
I think that it worked the second try has something to do with the sequence flow:
In the android/AndroidLauncher.java, onCreate:
MyGame myGame = new MyGame(this);
initialize(myGame, config);
// init IAP
myGame.setPlatformResolver(new AndroidResolver(myGame, this));
In core/MyGame.java, declarations:
public PurchaseObserver purchaseObserver = new BrainsPurchaseObserver();
public PurchaseManagerConfig purchaseManagerConfig;
In core/MyGame.java, constructor:
purchaseManagerConfig = new PurchaseManagerConfig();
Offer iap15Tipps = new Offer();
iap15Tipps.setIdentifier(Product.brains_hints_15.name());
iap15Tipps.setType(OfferType.CONSUMABLE);
purchaseManagerConfig.addOffer(iap15Tipps);
PlatformResolver.java and AndroidResolver.java as described in the tutorial. This worked to the point of the above error "no app store found".
Then I switched from gdx-pay 0.3.0 to 0.4.0 (by just incrementing the version in the gradle settings, it is already available in the repository), AND IT WORKED!
I suggest you check the sequence of IAP initializing you execute and switch to 0.4.0 if you are not already using it.
-- Michael