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();
}
}
);
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'm trying to construct a proxy service with Netty that will support streaming calls and HTTP/1 traffic.
I've managed to do this successfully but ran into problems when I tried adding an HttpObjectAggregator to the pipeline. I need the Aggregator in order to construct a FullHttpResponse for reporting.
My current setup uses 2 ChannelInitializers and 2 business logic handlers
To initiate the proxy service:
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(eventLoopGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new SourceTransportHandlerInitializer())
.childOption(ChannelOption.AUTO_READ, false);
Channel channel;
try {
channel = serverBootstrap.bind(localPort).sync().channel();
channel.closeFuture().sync();
}catch (InterruptedException e){
// oh no
}
SourceTransportHandlerInitializer.java
public class SourceTransportHandlerInitializer extends ChannelInitializer<SocketChannel>{
#Override
protected void initChannel(SocketChannel socketChannel) {
ChannelPipeline pipeLine = socketChannel.pipeline();
pipeLine.addLast(new HttpServerCodec(102400,102400,102400));
pipeLine.addLast(new HttpObjectAggregator(1048576));
pipeLine.addLast(new SourceHandler());
pipeLine.addLast(new LoggingHandler(LogLevel.INFO));
}
}
SourceHandler.java
public class SourceHandler extends ChannelInboundHandlerAdapter {
private volatile Channel outboundChannel;
#Override
public void channelActive(ChannelHandlerContext context) {
final Channel inChannel = context.channel();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(inChannel.eventLoop())
.channel(context.channel().getClass())
.handler(new TargetTransportHandlerInitializer(inChannel))
.option(ChannelOption.AUTO_READ, false);
ChannelFuture channelFuture = bootstrap.connect(Constants.host, Constants.hostPort);
outboundChannel = channelFuture.channel();
channelFuture.addListener((ChannelFutureListener) channelFuture1 -> {
if (channelFuture1.isSuccess()) {
inChannel.read();
} else {
inChannel.close();
}
});
}
#Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) {
if (msg instanceof FullHttpRequest) {
//record request
}
if (outboundChannel.isActive()) {
outboundChannel.writeAndFlush(msg).addListener((ChannelFutureListener) channelFuture -> {
if (channelFuture.isSuccess()) {
// flush and read data
ctx.channel().read();
} else {
channelFuture.channel().close();
}
});
} else {
LOG.debug("SourceHandler did not read. Outbound Channel not active");
}
}
...
}
TargetTransportHandlerInitializer.java
public class TargetTransportHandlerInitializer extends ChannelInitializer<SocketChannel>{
protected final Channel inChannel;
public TargetTransportHandlerInitializer (final Channel inChannel){
this.inChannel = inChannel;
}
#Override
protected void initChannel(SocketChannel socketChannel) {
ChannelPipeline pipeLine = socketChannel.pipeline();
pipeLine.addLast("codec", new HttpClientCodec(102400, 102400, 102400));
//pipeLine.addLast(new HttpObjectAggregator(1048576));
pipeLine.addLast(new TargetHandler(inChannel));
}
}
TargetHandler.java
public class TargetHandler extends ChannelInboundHandlerAdapter {
private final Channel inChannel;
public TargetHandler(Channel inChannel) {
this.inChannel = inChannel;
}
#Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.read();
}
#Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) {
if (msg instanceof FullHttpResponse ) {
//record response
} else if (msg instanceof DefaultHttpResponse) {
// convert to FullHttpResponse ?
}
inChannel.writeAndFlush(msg).addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
ctx.channel().read();
} else {
future.channel().close();
}
});
}
...
}
The HttpObjectAggregator in the source initializer pipeline causes no problems, and allows me to record the FullHttpRequest. If I comment pipeLine.addLast(new HttpObjectAggregator(1048576)); in the TargetTransportHandlerInitializer class the proxy works perfect. If I remove that comment the streaming calls begin to fail. Without the aggregator the msg object is only a DefaultHttpResponse for HTTP/1 calls and I can't access the body and headers.
Do I need some kind of conditional pipeline where streaming avoids the aggregator? Or is there some alternative way to construct a FullHttpResponse from a DefaultHttpResponse?
I was really hoping I could just do this FullHttpResponse full = HttpObjectAggregator.aggregate(default) and call it a day.
Netty's HTTP codec works in two distinct ways:
Aggregated: Use the object aggregator as you mention and it produces FullHttpRequest/FullHttpResponse objects.
Non-aggregated (streaming): It produces distinct messages for headers, payload and trailers, which are DefaultHttpRequest/Response, HttpContent and LastHttpContent.
The idiomatic netty way of converting messages is to add a handler in the pipeline, so you would not have APIs like FullHttpResponse full = HttpObjectAggregator.aggregate(default) but instead you will insert a handler after the HttpObjectAggregator and receive the FullHttpResponse object.
The above is an either/or choice, so if you add an aggregator, handlers after the aggregator will only see aggregated messages whereas before the aggregator, they will see the distinct messages as I mention above.
The following example demonstrates of how to handle streaming messages:
https://github.com/netty/netty/blob/4.1/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClientHandler.java#L30
I was able to accomplish this by making a slight modification to the PortUnification example in the Netty project.
I updated the TargetTransportHandlerInitializer with the following:
#Override
protected void initChannel(SocketChannel socketChannel) {
ChannelPipeline pipeLine = socketChannel.pipeline();
pipeLine.addLast("codec", new HttpClientCodec(102400, 102400, 102400));
pipeLine.addLast("switch", new SwitchHandler(inChannel));
}
SwitchHandler is a ChannelInboundHandlerAdapter with the following:
#Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) {
if (recordable(msg))
createRecordingChannel(ctx);
else
createStreamingChannel(ctx);
ctx.fireChannelRead(msg);
}
private void createRecordingChannel(ChannelHandlerContext ctx) {
ChannelPipeline p = ctx.pipeline();
p.addLast("aggregator", new HttpObjectAggregator(1048576));
p.addLast("recordingHandler", new RecordingTargetHandler(inChannel));
p.remove(this);
}
private void createStreamingChannel(ChannelHandlerContext ctx) {
ChannelPipeline p = ctx.pipeline();
p.addLast("simpleHandler", new SimpleTargetHandler(inChannel));
p.remove(this);
}
recordable() contains some business logic to look at the msg and make a determination. In my case was was looking at Headers.
My two new handlers look like the original TargetHandler, with different channelRead methods.
SimpleTargetHandler extends ChannelInboundHandlerAdapter with
#Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) {
inChannel.writeAndFlush(msg).addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
ctx.channel().read();
} else {
future.channel().close();
}
});
}
RecordingTargetHandler extends SimpleTargetHandler with:
#Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) {
if (msg instanceof FullHttpResponse ) {
// record response
}
super.channelRead(ctx, msg);
}
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))
);
i'm having a problem with rx java.
I have a current stream that in some point gives to me an Either
That response has external resources, like image urls, and i want to send each url to an external class, download it asyncronously, and if all of them are ok, continue with that either received or if one of that resources fails while is being downloaded return an Either.error(MyError());
My problem is that as i'm creating a new observable inside the resources provider, it needs to be subscribed to start run, but i do not know how can i do.
This is my current code (not sure if compiles but you get the idea):
private Observable<Either<Error, Response>> prefetchResourcesOrError(final Either<Error, Response> errorOrResponse) {
if (errorOrResponse.isResponseWithImages()) {
ResponseImages responseImages = (ResponseImages) responseImages.getResponse();
return
Observable.fromIterable(responseImages.getResources()
.map(resourcesProvider::prefetch)
.onErrorReturn(throwable -> Observable.<Either<Error, Response>>just(Either.left(new MyError())))
.map(observable -> errorOrResponse);
} else {
return Observable.just(errorOrResponse);
}
}
//Resource prefetch method
Observable prefetch(Resource resource) {
return Observable.just(resource)
.flatMap((Function<Resource, ObservableSource<?>>) res1 ->
Observable.create((ObservableOnSubscribe<Void>) emitter ->
resourceLoader.prefetch(res1.getUrl(), new ImageLoaderListenerAdapter() {
#Override
public void onException(Exception e) {
emitter.onError(e);
}
#Override
public void onResourceReady() {
emitter.onNext(null);
}
})
)
);
}
}
//The main Stream
//MainObservable is an Either<Error, Response> errorOrResponse
return mainObservable.flatMap(this::prefetchResourcesOrError);
In my GWT Application I'm often refering several times to the same server results. I also don't know which code is executed first. I therefore want to use caching of my asynchronous (client-side) results.
I want to use an existing caching library; I'm considering guava-gwt.
I found this example of a Guava synchronous cache (in guava's documentation):
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});
This is how I'm trying to use a Guava cache asynchronously (I have no clue about how to make this work):
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
// I want to do something asynchronous here, I cannot use Thread.sleep in the browser/JavaScript environment.
service.createExpensiveGraph(key, new AsyncCallback<Graph>() {
public void onFailure(Throwable caught) {
// how to tell the cache about the failure???
}
public void onSuccess(Graph result) {
// how to fill the cache with that result???
}
});
return // I cannot provide any result yet. What can I return???
}
});
GWT is missing many classes from the default JRE (especially concerning threads and concurrancy).
How can I use guava-gwt to cache asynchronous results?
As I understood what you want to achieve is not just a asynchronous cache but also a lazy cache and to create one the GWT is not a best place as there is a big problem when implementing a GWT app with client side Asynchronous executions, as GWT lacks the client side implementations of Futures and/or Rx components (still there are some implementations of RxJava for GWT). So in usual java what you want to create can be achieved by :
LoadingCache<String, Future<String>> graphs = CacheBuilder.newBuilder().build(new CacheLoader<String, Future<String>>() {
public Future<String> load(String key) {
ExecutorService service = Executors.newSingleThreadExecutor();
return service.submit(()->service.createExpensiveGraph(key));
}
});
Future<String> value = graphs.get("Some Key");
if(value.isDone()){
// This will block the execution until data is loaded
String success = value.get();
}
But as GWT has no implementations for Futures you need to create one just like
public class FutureResult<T> implements AsyncCallback<T> {
private enum State {
SUCCEEDED, FAILED, INCOMPLETE;
}
private State state = State.INCOMPLETE;
private LinkedHashSet<AsyncCallback<T>> listeners = new LinkedHashSet<AsyncCallback<T>>();
private T value;
private Throwable error;
public T get() {
switch (state) {
case INCOMPLETE:
// Do not block browser so just throw ex
throw new IllegalStateException("The server response did not yet recieved.");
case FAILED: {
throw new IllegalStateException(error);
}
case SUCCEEDED:
return value;
}
throw new IllegalStateException("Something very unclear");
}
public void addCallback(AsyncCallback<T> callback) {
if (callback == null) return;
listeners.add(callback);
}
public boolean isDone() {
return state == State.SUCCEEDED;
}
public void onFailure(Throwable caught) {
state = State.FAILED;
error = caught;
for (AsyncCallback<T> callback : listeners) {
callback.onFailure(caught);
}
}
public void onSuccess(T result) {
this.value = result;
state = State.SUCCEEDED;
for (AsyncCallback<T> callback : listeners) {
callback.onSuccess(value);
}
}
}
And your implementation will become :
LoadingCache<String, FutureResult<String>> graphs = CacheBuilder.newBuilder().build(new CacheLoader<String, FutureResult<String>>() {
public FutureResult<String> load(String key) {
FutureResult<String> result = new FutureResult<String>();
return service.createExpensiveGraph(key, result);
}
});
FutureResult<String> value = graphs.get("Some Key");
// add a custom handler
value.addCallback(new AsyncCallback<String>() {
public void onSuccess(String result) {
// do something
}
public void onFailure(Throwable caught) {
// do something
}
});
// or see if it is already loaded / do not wait
if (value.isDone()) {
String success = value.get();
}
When using the FutureResult you will not just cache the execution but also get some kind of laziness so you can show some loading screen while the data is loaded into cache.
If you just need to cache the asynchronous call results, you can go for a
Non-Loading Cache, instead of a Loading Cache
In this case you need to use put, getIfPresent methods to store and retrieve records from cache.
String v = cache.getIfPresent("one");
// returns null
cache.put("one", "1");
v = cache.getIfPresent("one");
// returns "1"
Alternatively a new value can be loaded from a Callable on cache misses
String v = cache.get(key,
new Callable<String>() {
public String call() {
return key.toLowerCase();
}
});
For further reference: https://guava-libraries.googlecode.com/files/JavaCachingwithGuava.pdf