I am very new to RxJava and have a problem. I try to chain two Completables. So the scenario is to update some Category Objects and afterwards update the UI. Both tasks will be executed within a Completable. I chained those two with the andThen operator but sometimes the UI Completable does not get executed and i dont get any error.
Disposable d = FilterController.getInstance().updateChosenCategoriesCompletable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.andThen(FilterController.getInstance().updateChosenCategoriesInterfaceCompletable())
.subscribeWith(new DisposableCompletableObserver() {
#Override
public void onComplete() {
Log.d("MultiselectDialog", "Categories Update Success");
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
ExceptionHandler.saveException(e, null, null);
Log.d("MultiselectDialog", "error");
}
});
I am not sure but i think this is caused by executing the first Completable on the IO Thread. Is this forbidden when i try to chain two Completables? The weird thing about it is that the second Completable does not get execute sometimes (1 out of 10 Times for example). When i insert a delay or do everything on the Main Thread then everything works. I just want to know if its forbidden to chain two Completables with two different threads.
Thanks for your help in advance!
/edit
The updateChosenCategoriesCompletable() is using this Completable:
public Completable setChosenCategoryCompletable(final ArrayList<CategoryHolder> category, final boolean callFilterOptionsChanged) {
return Completable.fromAction(new Action() {
#Override
public void run() throws Exception {
try {
Log.d("Workflow", "setChosenCategoryCompletable " + category.size());
Log.d("Workflow", "setChosenCategoryCompletable " + Thread.currentThread().getName());
mChosenCategories = category;
if (callFilterOptionsChanged) {
mFilterOptionsChangedListener.filterOptionsChanged();
}
} catch (Exception e){
Log.d("Workflow", "error set");
e.printStackTrace();
}
}
});
}
and the updateChosenCategoriesCompletable() is using this Completable
public Completable updateCategoryLabelTextCompletable(final ArrayList<CategoryHolder> categories) {
return Completable.fromAction(new Action() {
#Override
public void run() throws Exception {
Log.d("Workflow", "updateCategoryLabelTextCompletable " + categories.size());
Log.d("Workflow", "updateCategoryLabelTextCompletable " + Thread.currentThread().getName());
TextView selectedValue = mLabels.get(mActivity.getString(R.string.CATEGORY));
ImageView categoryIcon = (ImageView) mRootView.findViewById(R.id.category_icon);
if(categories.size() > 1) {
selectedValue.setText(String.valueOf(categories.size()));
categoryIcon.setVisibility(View.GONE);
}
else if(categories.isEmpty()) {
selectedValue.setText(mActivity.getString(R.string.CATEGORY_EMPTY));
categoryIcon.setVisibility(View.GONE);
}
else {
selectedValue.setText(categories.get(0).getCategory());
// Set category image if only one category is selected
categoryIcon.setVisibility(View.VISIBLE);
String categoryImage = categories.get(0).getCategoryImage();
if(!categoryImage.isEmpty()){
int imageResource = mActivity.getResources().getIdentifier(categoryImage, "drawable", mActivity.getPackageName());
Drawable image = null;
// getDrawable(int) is deprecated since API 21
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
image = mActivity.getResources().getDrawable(imageResource, null);
} else {
image = mActivity.getResources().getDrawable(imageResource);
}
}catch (Exception e){
e.printStackTrace();
}
if(image != null){
categoryIcon.setImageDrawable(image);
}
}else {
categoryIcon.setVisibility(View.GONE);
}
}
}
});
}
Is it mandatory to use fromCallable instead?
This is the method that updates the button i mentioned:
public void setResultButtonText(final String text, final boolean buttonEnabled) {
if(mShowResultsButton != null) {
mActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
try {
mShowResultsButton.setText(text);
mShowResultsButton.setEnabled(buttonEnabled);
if (buttonEnabled) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mShowResultsButton.setBackground(mActivity.getDrawable(R.drawable.ripple));
} else {
mShowResultsButton.setBackground(mActivity.getResources().getDrawable(R.drawable.ripple));
}
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mShowResultsButton.setBackground(mActivity.getDrawable(R.drawable.ripple_grey));
} else {
mShowResultsButton.setBackground(mActivity.getResources().getDrawable(R.drawable.ripple_grey));
}
}
} catch (Exception e){
e.printStackTrace();
Log.d("Filterinterface", "ERROR");
}
}
});
}
}
Related
I have a button in my MapsActivity that communicates to the Wear app. The following Thread is executed:
class NewThread extends Thread {
String path;
String message;
NewThread(String p, String m) {
path = p;
message = m;
}
public void run() {
Task<List<Node>> wearableList =
Wearable.getNodeClient(getApplicationContext()).getConnectedNodes();
try {
List<Node> nodes = Tasks.await(wearableList);
for (Node node : nodes) {
Task<Integer> sendMessageTask =
Wearable.getMessageClient(MapsActivity.this).sendMessage(node.getId(), path, message.getBytes());
try {
Integer result = Tasks.await(sendMessageTask);
sendmessage("I just sent the wearable a message " + sentMessageNumber++);
} catch (final ExecutionException exception) {
MapsActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
Toasty.error(MapsActivity.this, "[1] Something went wrong. Error details: " + exception.getMessage()).show();
}
});
} catch (final InterruptedException exception) {
MapsActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
Toasty.error(MapsActivity.this, "[2] Something went wrong. Error details: " + exception.getMessage()).show();
}
});
}
}
} catch (final ExecutionException exception) {
MapsActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
Toasty.error(MapsActivity.this, "[3] Something went wrong. Error details: " + exception.getMessage()).show();
}
});
} catch (final InterruptedException exception) {
MapsActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
Toasty.error(MapsActivity.this, "[4] Something went wrong. Error details: " + exception.getMessage()).show();
}
});
}
}
}
But I get the following error in the image when trying to accomplish the Thread.
This happens on an Emulator where the WearOS app/api is not available to me (Probably on real devices which do not have WearOS App/Api too).
It happens on the Wearable.getMessageClient(MapsActivity.this).sendMessage(node.getId(), path, message.getBytes());.
I didn't find a clean way of checking the Wear API to be available so I am trying to get the capabilities first, catching the ApiException and setting a isWearableAvailable property:
private fun checkCapability() {
try {
val capability = capabilityClient
.getCapability(CAPABILITY_XYZ, CapabilityClient.FILTER_REACHABLE)
.await()
isConnected = capability.nodes.any(Node::isNearby)
if (BuildConfig.DEBUG) Log.d(LOG_TAG,
"Capability: ${capability.nodes.joinToString()}} > ${isConnected}")
isWearableApiAvailable = true
} catch (e: ApiException) {
isWearableApiAvailable = false
}
}
I am trying to call the web service to fetch the data and storing it into database using following code. I have created a separate class to perform following operation.
Now , the issue is i want to notify my activity when i successfully fetch and store data in database. if some error occurs then i want to show that on UI itself.
somehow i am able to write a code to fetch the data using pagination but not sure how would i notify UI where i can subscribe catch the update related to progress and error if any.
public Flowable<Response> getFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
fitnessFlowable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.takeUntil(response->response.getSummary().getNext()!=null)
.subscribe(new Subscriber<Response>() {
#Override
public void onSubscribe(Subscription s) {
s.request(Long.MAX_VALUE);
}
#Override
public void onNext(Response response) {
Log.e(TAG, "onNext" );
if(response !=null){
if(response.getFitness()!=null && response.getFitness().size()!=0){
Realm realm = Realm.getDefaultInstance();
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealmOrUpdate(response.getFitness());
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
Log.i(TAG, "onSuccess , fitness data saved");
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
Log.i(TAG, "onError , fitness data failed to save"+error.getMessage() );
}
});
}else{
Log.i(TAG, "onError , no fitness data available" );
}
}else{
Log.i(TAG, "onError , response is null" );
}
}
#Override
public void onError(Throwable t) {
Log.e(TAG, "onError" +t.getMessage());
}
#Override
public void onComplete() {
Log.e(TAG, "onComplete");
}
});;
return null;
}
Updated
Created RxBus to propagate events and error on UI
public class RxBus {
private static final RxBus INSTANCE = new RxBus();
private RxBus(){}
private PublishSubject<Object> bus = PublishSubject.create();
public static RxBus getInstance() {
return INSTANCE;
}
public void send(Object o) {
bus.onNext(o);
}
public void error(Throwable e){bus.onError(e);}
public Observable<Object> toObservable() {
return bus;
}
}
in activity
FitnessRepo fitnessRepo = new FitnessRepo();
fitnessRepo.getFitnessData();
RxBus.getInstance().toObservable().subscribe(new Observer<Object>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Object o) {
if(o instanceof RealmList ){
RealmList<Fitness> realmList = (RealmList<Fitness>) o;
Log.e(TAG,"Fitness data size "+realmList.size());
}
}
#Override
public void onError(Throwable e) {
Log.e(TAG,e.getMessage()+ "");
if (e instanceof HttpException) {
ResponseBody body = ((HttpException) e).response().errorBody();
try {
Response response= LoganSquare.parse(body.byteStream(),Response.class);
if(response.getErrors() !=null)
if(response.getErrors().size() > 0)
Log.e(TAG, "Error "+response.getErrors().get(0).getErrors());
} catch (IOException t) {
t.printStackTrace();
}
}
}
#Override
public void onComplete() {
}
});
in a web service call
public void getFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
request.setEnd_date("2018-07-01T00:00:00");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
fitnessFlowable.subscribeOn(Schedulers.io())
.takeUntil(response->response.getSummary().getNext()!=null)
.doOnNext((response) -> {
if(response ==null || response.getFitness() == null || response.getFitness().isEmpty()) {
Log.e(TAG, " Error ");
return;
}
RxBus.getInstance().send(response.getFitness());
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction((realm) -> {
realm.copyToRealmOrUpdate(response.getFitness());
});
}
}).subscribe(item ->{
},
error ->{
RxBus.getInstance().error(error);
});
}
I have good news for you! You can delete almost all of that code and just make it generally better as a result!
public void fetchFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
fitnessFlowable.subscribeOn(Schedulers.io())
.takeUntil(response->response.getSummary().getNext()!=null)
.doOnNext((response) -> {
if(response ==null || response.getFitness() == null || response.getFitness().isEmpty()) return;
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction((realm) -> {
realm.insertOrUpdate(response.getFitness());
});
}
}
}).subscribe();
}
This method is on a background thread now and returns void, so the way to emit stuff out of this method would be to use either a PublishSubject (one for success, one for failure) or an EventBus.
private PublishSubject<Object> fitnessResults;
public Observable<Object> observeFitnessResults() {
return fitnessResults;
}
public static class Success {
public Success(List<Fitness> data) {
this.data = data;
}
public List<Fitness> data;
}
public static class Failure {
public Failure(Exception exception) {
this.exception = exception;
}
public Exception exception;
}
public void fetchFitnessData() {
...
fitnessResults.onNext(new Success(data));
} catch(Exception e) {
fitnessResults.onNext(new Failure(e));
And then
errors = observeFitnessResults().ofType(Error.class);
success = observeFitnessResults().ofType(Success.class);
There are different ways to achieve this. I will never handle the subscriptions on my own out of a lifecycle scope as it creates a possibility of memory leak. In your case it seems that both success and failure is bound to the UI so you can simply do this.
public Completable fetchFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
return fitnessFlowable.subscribeOn(Schedulers.io())
.takeUntil(response->response.getSummary().getNext()!=null)
.doOnNext((response) -> {
if(response ==null || response.getFitness() == null || response.getFitness().isEmpty()) return;
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction((realm) -> {
realm.insertOrUpdate(response.getFitness());
});
}
}
}).ignoreElements();
}
At UI level, you can just handle your subscription with both success and failure. In case you need success model can replace Completable with Single or Flowable.
fetchFitnessData.subscrible(Functions.EMPTY_ACTION, Timber::d);
The major advantage with this approach is that you handle your subscription lifecycles.
i am posting event using EventBus inside a recursive function that fetches pagination data from the webservice.
public void getCallsData(final UserRequest userRequest){
serviceCall.enqueue(new Callback<UserResponseInfo>() {
#Override
public void onResponse(Call<UserResponseInfo> call, Response<UserResponseInfo> response) {
if(response.isSuccessful()) {
UserResponseInfo userResponseInfo = response.body();
if (userResponseInfo != null) {
try {
Log.e(TAG, "getCallsData response " + LoganSquare.serialize(userResponseInfo));
} catch (IOException e) {
e.printStackTrace();
}
int currentPage = userRequest.getUserRequestInfo().get(0).getPage();
int totalPages = userResponseInfo.getTotalPages();
if(currentPage < totalPages){
userRequest.getUserRequestInfo().get(0).setPage(++currentPage);
Log.e(TAG, "getCallsData fetching next page "+currentPage);
userResponseInfo.setCurrentPage(currentPage);
userResponseInfo.setRequestType(GET_CALL_REQUEST);
EventBus.getDefault().postSticky(userResponseInfo);
getCallsData(userRequest);
}
} else {
}
}else{
}
}
#Override
public void onFailure(Call<UserResponseInfo> call, Throwable t) {
}
});
}
The issue is EventBus.getDefault().postSticky(userResponseInfo); when getCall executes in recursive fashion its not posting the event properly as in only first event is getting called and it misses the last one.
I am currently using the Retrofit2.0 to poll the server .I am getting the result in x second but the problem is page number is not updating in the API.Lets come to the code for better clarification
private void startPolling() throws Exception {
Log.e("APP CONSTANT","" + current_page);
MrSaferWebService service = ServiceFactory.createRetrofitService(MrSaferWebService.class, AppConstants.BASE_URL);
final Observable<ReportResponse> reportResponseObservable = service.getListOfInciden("get_report", current_page, 5, incident_lat, incident_long);
Observable.interval(0,20,TimeUnit.SECONDS)
.flatMap(new Func1<Long, Observable<ReportResponse>> () {
#Override
public Observable<ReportResponse> call(Long aLong) {
return reportResponseObservable;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<ReportResponse>() {
#Override
public void call(ReportResponse response) {
Log.i("HEARTBEAT_INTERVAL", "Response from HEARTBEAT");
ActivityUtils.showProgress(false, mRootView, mProgressView, mContext);
if (response.getStatus() == 1) {
current_page = current_page + 1;
if (!response.getReportList().isEmpty()) {
addItems(response.getReportList());
}
else{
//do nothing
}
} else {
Log.e("MY ERROR", "" + "SOME ERROR OCCURED");
}
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
ActivityUtils.showProgress(true, mRootView, mProgressView, mContext);
// TODO: 22/03/16 ADD ERROR HANDLING
}
});
}
As you can see i have incremented the current_page by 1 every time on
SuccessFull Response but when i check the Log the current_page value are increased only once and after that log are not there and hence there value is also not increasing..So it taking the the same page number every time and giving me the Duplicate response.
Please help me to find what i am missing.
After spending more than a day i just changed Action with Subscriber and everything seems to be working .I don't know what happen internally but it works . I am still trying to figure it out what the difference between Action and Subscriber.
Below are my updated code which did the tricks.
private void startPolling() throws Exception {
final MrSaferWebService service = ServiceFactory.createRetrofitService(MrSaferWebService.class, AppConstants.BASE_URL);
Observable
.interval(0,20,TimeUnit.SECONDS)
.flatMap(new Func1<Long, Observable<ReportResponse>>() {
#Override
public Observable<ReportResponse> call(Long aLong) {
Log.e("PAGE", "" + current_page);
return service.getListOfInciden("get_report", current_page, 5, incident_lat, incident_long);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<ReportResponse>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
if (mProgressView !=null && mRootView !=null) {
ActivityUtils.showProgress(false, mRootView, mProgressView, mContext);
}
}
#Override
public void onNext(ReportResponse response) {
if (mProgressView !=null && mRootView !=null) {
ActivityUtils.showProgress(false, mRootView, mProgressView, mContext);
}
if (response.getStatus() == 1) {
if (!response.getReportList().isEmpty()){
current_page = current_page + 1;
addItems(response.getReportList());
}
else{
//do nothing
}
} else {
Log.e("MY ERROR", "" + "SOME ERROR OCCURED");
}
}
});
}
I want to use RxAndroid in my project,
and i make the thread sleep for 50ms
but it caused anr,the code
public void getTypeAndCommodity() {
Observable.from(getCommodities())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Commodity>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Commodity commodity) {
}
});
}
and the getCommodities:
private ArrayList<Commodity> getCommodities() {
// some test info
ArrayList<Commodity> list = new ArrayList<>();
for (int i = 0; i < 99; i++) {
Commodity commodity = new Commodity();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
commodity.setName("name" + i);
commodity.setType("type" + (i + 1) / 10);
list.add(commodity);
}
return list;
}
why it cause anr?please help
This happens because getCommodities() is executed in main thread, and only the item emited is executed in io thread with subscribeOn(Schedulers.io()). If you want to execute getCommidities() in background thread too, you need to create an observable with defer() method:
Observable.defer(new Func0<Observable<Object>>() {
#Override public Observable<Object> call() {
return Observable.from(getCommodities());
}
}).subscribeOn(Schedulers.io())...
If you need more info: http://blog.danlew.net/2015/07/23/deferring-observable-code-until-subscription-in-rxjava/