I want to display progressDialog while observable is downloading file , and when it's done want to send file to subscriber.
I tried to make my custom subscriber by extends from Subscriber for example:
public abstract class MySubscriber<T> extends Subscriber {
abstract void onMessage(String message);
abstract void onDownloaded(File file);
}
and tried to subscribe with it:
`
MySubscriber mySubscriber = new MySubscriber() {
#Override
public void onMessage(String message) {
progessDialog.setMessage(message);
}
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Object o) {
}
};
observable.subscribe(mySubscriber);
observable is :
observable = Observable.create(new Observable.OnSubscribe<Void>() {
#Override
public void call(Subscriber<Void> subscriber) {
//file downloading code...
if (subscriber instanceof MySubscriber){
((MySubscriber) subscriber).onMessage("100%");
((MySubscriber) subscriber).onDownloaded(file);
}else{
Log.e(TAG,"subscriber is not instance of MySubscriber")
}
}
And answer is "subscriber is not instance of MySubscriber"
The reason for subscriber not being of type MySubscriber is because the instance you pass is eventually wrapped by subscribe() in SafeSubscriber:
private static <T> Subscription subscribe(Subscriber<? super T> subscriber, Observable<T> observable) {
...
if(!(subscriber instanceof SafeSubscriber)) {
subscriber = new SafeSubscriber((Subscriber)subscriber);
}
...
}
}
If you want to keep using your approach, you can cast subscriber to SafeSubscriber and call SafeSubscriber#getActual() on it to get your instance of MySubscriber.
In your case:
Observable.create(new Observable.OnSubscribe<Void>() {
#Override
public void call(Subscriber<? super Void> subscriber) {
Subscriber yourSubscriber = ((SafeSubscriber) subscriber).getActual();
((MySubscriber) yourSubscriber).onMessage("100%");
((MySubscriber) yourSubscriber).onDownloaded(file);
}
});
Related
I'm creating an user event system using JDK 9 Flow API, so I have a room (which extends the UserSubscriver class above), it may have many users and each user can offer (dispatch) updates at any time.
public abstract class UserSubscriver implements Flow.Subscriber<Notification> {
private Flow.Subscription subscription;
#Override
public void onSubscribe(final Flow.Subscription subscription) {
this.subscription = subscription;
subscription.request(1);
}
#Override
public void onError(final Throwable throwable) {
// ...
}
#Override
public void onNext(final Notification notification) {
// ...
subscription.request(1);
}
#Override
public void onComplete() {
// How can I know who was the publisher of this?
}
}
User class:
public class User extends SubmissionPublisher<Notification> {
....
public int offer(Notification item) {
return super.offer(item, (sub, msg) -> false);
}
}
On the onUpdate I can receive any args, so I can receive the publisher of the update, but there are no args on onComplete.
How can I know who was the publisher of an onComplete event?
I've created a custom implementation of Call<T>, here is the custom class without the custom code, just the forward code for you to see.
public class CachedCall<T> implements Call<T> {
private final Call<T> delegate;
public CachedCall(Call<T> delegate) {
this.delegate = delegate;
}
#Override
public Response<T> execute() throws IOException {
return delegate.execute();
}
#Override
public void enqueue(Callback<T> callback) {
delegate.enqueue(callback);
}
public void enqueueWithCache(final CachedCallback<T> callback) {
delegate.enqueue(callback);
}
#Override
public boolean isExecuted() {
return delegate.isExecuted();
}
#Override
public void cancel() {
delegate.cancel();
}
#Override
public boolean isCanceled() {
return delegate.isCanceled();
}
#Override
public Call<T> clone() {
return new CachedCall<>(delegate.clone());
}
#Override
public Request request() {
return delegate.request();
}
}
And then in my ApiService, I used this custom implementation on some of my call, and the default one on some other, exemple:
public interface APIService {
#GET("categories")
Call<List<Categorie>> categories(#Query("tag") String tag);
#GET("categories/{categorie}/quotes")
CachedCall<List<Gif>> gifs(#Path("categorie") String categorie);
When methods with the custom one are called, I got a crash:
Caused by: java.lang.IllegalArgumentException: Could not locate call adapter for CustomClass.
Tried:
* retrofit2.adapter.rxjava.RxJavaCallAdapterFactory
* retrofit2.ExecutorCallAdapterFactory
at retrofit2.Retrofit.nextCallAdapter(Retrofit.java:237)
at retrofit2.Retrofit.callAdapter(Retrofit.java:201)
at retrofit2.ServiceMethod$Builder.createCallAdapter(ServiceMethod.java:232)
... 21 more
Do I need to register my custom implementation with Retrofit somewhere?
I've solved my own issue.
You need to create and register your own CallAdapter.Factory:
public class CachedCallAdapterFactory extends CallAdapter.Factory {
final Executor callbackExecutor;
public CachedCallAdapterFactory(Executor callbackExecutor) {
this.callbackExecutor = callbackExecutor;
}
#Override
public CallAdapter<Call<?>> get(final Type returnType, final Annotation[] annotations, final Retrofit retrofit) {
if (getRawType(returnType) != CachedCall.class) {
return null;
}
final Type responseType = getParameterUpperBound(0, (ParameterizedType) returnType);
return new CallAdapter<Call<?>>() {
#Override public Type responseType() {
return responseType;
}
#Override public <R> Call<R> adapt(Call<R> call) {
return new CachedCall<>(callbackExecutor, call, responseType, retrofit, annotations);
}
};
}
}
And then register it when you create your Retrofit instance:
retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(URL)
.addCallAdapterFactory(new CachedCallAdapterFactory(new DefaultExecutor()))
.build();
Your DefaultExecutor just need to run its Runnable
private class DefaultExecutor implements Executor {
#Override
public void execute(#NonNull Runnable runnable) {
runnable.run();
}
}
I thought of creating a separate class for all of the smack's basic methods like connecting, login, sending message, receiving messages.
So, there's a listener method which receives messages.
static ChatManagerListener chatManagerListener = new ChatManagerListener() {
#Override
public void chatCreated(Chat chat, boolean createdLocally) {
chat.addMessageListener(
new ChatMessageListener() {
#Override
public void processMessage(Chat chat, Message message) {
System.out.println("MESSAGE RECEIVED: "+message.toString());
messageReceived(message);
}
});
}
};
Message is received and passed to messageReceived() method.
SITUATION:
Now, when I import this class into other, I would like to extend the functionality of this messageReceived() method, so the whole process remains abstract and the developer only deals with incoming messages. Or, somehow this messageReceived() method push the message to that other class.
Basically you need to define another listner to manage the message.
This is a working snippet example (of a prototype, so it's ugly and without any pattern) to update the GUI of reciver user.
If you'll need something else keep in mind that you'll need plugins (PacketInterceptor) on server side.
/*MessageGuiUpdate.java*/
public interface MessageGuiUpdate {
public void displayMessage(String body);
}
/*XmppManager.java*/
public void init() throws XMPPException, SmackException, IOException {
private MessageGuiUpdate guiUpdate;
//FOO
//BAR
/* init() */
this.chatManager = ChatManager.getInstanceFor(connection);
this.chatManager.addChatListener(
new ChatManagerListener() {
#Override
public void chatCreated(Chat chat, boolean createdLocally)
{
if (!createdLocally)
{
chat.addMessageListener(new IncomingMessageListener());;
}
}
});
}
/*nested class*/
class IncomingMessageListener implements ChatMessageListener {
#Override
public void processMessage(Chat arg0, Message message) {
String from = message.getFrom();
String body = message.getBody();
if (body != null)
{
System.out.println(String.format("============ Received message '%1$s' from %2$s\n============", body, from));
guiUpdate.displayMessage(body);
}
}
}
/*CustomGui.java*/
public class CustomGui implements MessageGuiUpdate {
//foo
#Override
public void displayMessage(String message) {
System.out.println("I've just recived: "+message);
}
}
I have some methods in a class like this:
#Override
public void sendRemoteRecord(String token, int channelId, int eventId, final ServiceCallback<RemoteRecordResponse> callback) {
epgServicesApiManager.sendRemoteRecord(token, channelId, eventId)
.observeOn(scheduler)
.subscribe(new Action1<RemoteRecordResponse>() {
#Override
public void call(RemoteRecordResponse model) {
if (callback != null)
callback.onSuccess(model);
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
if (callback != null)
callback.onError();
}
});
}
#Override
public void activateRemoteRecord(String token, String cardNumber, final ServiceCallback<RemoteRecordActivateResponse> callback) {
epgServicesApiManager.activateRemoteRecord(token, cardNumber)
.observeOn(scheduler)
.subscribe(new Action1<RemoteRecordActivateResponse>() {
#Override
public void call(RemoteRecordActivateResponse remoteRecordActivateResponse) {
if (callback != null)
callback.onSuccess(remoteRecordActivateResponse);
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
if (callback != null)
callback.onError();
}
});
}
Is it possible to remove the duplication around the code after the observeOn() line?
The annoying part is making sure I do the null check on the callback before using it.
At present, I know of seven distinct methods I need in this class and possibly more.
Unfortunately, in Java 1.7 there is no way to fix this without increasing the amount of code. You can reduce the amount of code needed locally, by introducing some helper classes.
One solution is to move your anonymous inner classes to top-level classes. From there you can introduce a dummy callback and some null-checking work an an abstract class.
It may end up looking something like this (horizontal rules are used to highlight that these classes are in separate files).
This is a dummy callback class, it does exactly nothing, but is safe to call against. This will replace the null values.
public class NullServiceCallBack<T> implements ServiceCallBack<T> {
#Override
public void onSuccess(T target) {}
#Override
public void onError() {}
}
This is an abstract class that handles the validation, converting null values to instances of NullServiceCallback:
public abstract class CallBackAction<T> implements Action1<T> {
private final ServiceCallBack<T> Callback;
public CallBackAction(ServiceCallBack<T> callback) {
this.Callback = (null != callback) ? callback : new NullServiceCallBack<>();
}
protected ServiceCallBack<T> getCallback() {
return Callback;
}
}
This is the concrete class you'll use for success.
public class SuccessCallbackAction<T> extends CallBackAction<T> {
public SuccessCallbackAction(ServiceCallBack<T> callback) {
super(callback);
}
#Override
public void call(T target) {
getCallback().onSuccess(target);
}
}
This is the concrete class for errors. This doesn't do anything with the arguments to call, so we can make this implement for Object once and be done with it.
public class ErrorCallbackAction extends CallBackAction<Object> {
public ErrorCallbackAction(ServiceCallBack<Object> callback) {
super(callback);
}
#Override
public void call(Throwable target) {
getCallback().onError();
}
}
So in the end, your example above should look something like this:
#Override
public void sendRemoteRecord(String token, int channelId, int eventId, final ServiceCallback<RemoteRecordResponse> callback) {
epgServicesApiManager.sendRemoteRecord(token, channelId, eventId)
.observeOn(scheduler)
.subscribe(new SuccessCallbackAction<RemoteRecordResponse>(callback),
new ErrorCallbackAction(callback));
}
#Override
public void activateRemoteRecord(String token, String cardNumber, final ServiceCallback<RemoteRecordActivateResponse> callback) {
epgServicesApiManager.activateRemoteRecord(token, cardNumber)
.observeOn(scheduler)
.subscribe(new SuccessCallbackAction<RemoteRecordActivateResponse>(callback),
new ErrorCallbackAction(callback));
}
Locally, we've reduced the amount of code, and made the intent a little more clear. Globally, we've increased the complexity with the addition of 4 new classes. Whether this is worth it depends on the context your code lives in, and is your call.
Introduce a dummy callback that does nothing, then do safeCallback().onSuccess() or safeCallback().onError()
Also, you can do this:
class SuccessCallback<T> extends Action1<T>() {
#Override
public void call(T value) {
safeCallback().onSuccess(value);
}
}
class ErrorCallback extends Action1<Throwable>() {
#Override
public void call(T value) {
safeCallback().onError();
}
}
then...
subscribe(new SuccessCallback<RemoteRecordActivateResponse>(), new ErrorCallback());
Does this work?
When I call the Retrofit method GetTodoRepository.fetchTodo() from MainViewModel and call ends in a failure or any non-success result, I would like to let RxJava to both do onErrorReturn() and onError() so I can return a cached object in that case, but still notify MainViewModel that an error happend, so I can show error-related UI views. How do I archive this?
The current code shows how I intended to handle it.
MainViewModel
public class MainViewModel extends ViewModel
public LiveData<String> getTodo() {
getTodoRepository.fetchTodo().subscribe(new SingleObserver<String>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(String s) {
showProgressAnim.setValue(false);
todo.setValue(s);
}
#Override
public void onError(Throwable e) {
showProgressAnim.setValue(false);
errorMsg.setValue(e.getMessage());
}
});
return todo;
}
}
GetTodoRepository
public class GetTodoRepository {
public Single<String> fetchTodo() {
return retrofit.create(TodoApi.class)
.getTodo()
.doOnSuccess(s -> cacheManager.saveTodo(s))
.onErrorReturn(throwable -> cacheManager.getTodo())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}
You can't have both signal types with a Single but you can turn fetchTodo() into Observable and emit the cached item and the error together:
fetchTodo()
.toObservable()
.onErrorResumeNext(error ->
Observable.just(cached)
.concatWith(Observable.error(error))
)
First approach which I mentioned in the comment is as follows
create a holder class for the result
class ToDoResult {
boolean isCached;
String todo;
Throwable error; // this will be set only in case of error
public ToDoResult(String todo, boolean isCached) {
this.isCached = isCached;
this.todo = todo;
}
public void setError(Throwable error) {
this.error = error;
}
}
Then make your fetchTodo() return Single<ToDoResult> instead of Single<String> as follows
public class GetTodoRepository {
public Single<ToDoResult> fetchTodo() {
return retrofit.create(TodoApi.class)
.getTodo()
.doOnSuccess(s -> cacheManager.saveTodo(s))
.map(todo -> new ToDoResult(todo,false))
.onErrorReturn(throwable -> {
ToDoResult toDoResult = new ToDoResult(cacheManager.getTodo(), true);
toDoResult.setError(throwable);
return toDoResult;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}
And in you ViewModel
getTodoRepository.fetchTodo().subscribe(new SingleObserver<ToDoResult>() {
#Override
public void onSuccess(ToDoResult toDoResult) {
showProgressAnim.setValue(false);
if (toDoResult.error != null) {
errorMsg.setValue(toDoResult.error.getMessage());
} else {
todo.setValue(toDoResult.todo);
}
}
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onError(Throwable e) {
showProgressAnim.setValue(false);
errorMsg.setValue(e.getMessage());
}
});
In this approach, your onError will never get called since we are converting the error to a success signal always.
Second approach is as per #akarnokd mentioned in the previous answer to use an Observable and trigger the onNext and onError back to back.
public class GetTodoRepository {
public Observable<String> fetchTodo() {
return retrofit.create(TodoApi.class)
.getTodo()
.doOnSuccess(s -> cacheManager.saveTodo(s))
.toObservable()
.onErrorResumeNext(error ->
Observable.just(cached)
.concatWith(Observable.error(error))
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}
And change your view model like this
getTodoRepository.fetchTodo().subscribe(new Observer<String>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(String s) {
// this will be triggered with the todo item (cached in case of error)
}
#Override
public void onError(Throwable e) {
// this will be triggered followed by onNext in case of error
}
#Override
public void onComplete() {
}
});