rx-android (or rx-java) with refreshlayout - android

I'm using a swiperefreshlayout android.support.v4.widget.SwipeRefreshLayout, and using rxjava to query to server every time a swipe happens. My code is like below, I have to create duplicate observable and subscriber inside the OnRefreshListener for them to get called, which looks quite bad due to code duplication. If I use the original ones (declared outside), then the subscriber is never returned (no onNext, onError, onCompleted triggered). What am I missing with RxJava in this case?
Subscriber<ListVerifyResponseWrapper> subscriber = new Subscriber<ListVerifyResponseWrapper>() {
#Override
public void onCompleted() {
LogUtils.LOGD(TAG, "completed");
}
#Override
public void onError(Throwable e) {
Toast.makeText(getActivity(), getString(R.string.An_error_has_occured), Toast.LENGTH_SHORT).show();
mSwipeRefreshLayout.setRefreshing(false);
}
#Override
public void onNext(ListVerifyResponseWrapper listVerifyResponseWrapper) {
changeViewStateAccordingToResult(listVerifyResponseWrapper);
}
};
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
Observable<ListVerifyResponseWrapper> observableDuplicate = RestClient.getInstance().getRestApi().getListVerifyRequestDetail(model);
final Subscriber<ListVerifyResponseWrapper> subscriberDuplicate = new Subscriber<ListVerifyResponseWrapper>() {
#Override
public void onCompleted() {
LogUtils.LOGD(TAG, "completed");
}
#Override
public void onError(Throwable e) {
Toast.makeText(getActivity(), getString(R.string.An_error_has_occured), Toast.LENGTH_SHORT).show();
mSwipeRefreshLayout.setRefreshing(false);
}
#Override
public void onNext(ListVerifyResponseWrapper listVerifyResponseWrapper) {
changeViewStateAccordingToResult(listVerifyResponseWrapper);
}
};
observableDuplicate.observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.newThread()).subscribe(subscriberDuplicate);
}
});
observable.observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.newThread()).subscribe(subscriber);
Thanks

I had the exact same problem. I solved it by chaining
.observeOn(AndroidSchedulers.mainThread())
right before the final subscriber. Apparently you have to make sure that stuff concerning the UI is done on the UI thread

On top of Johnnycube's answer (which is correct), I suggest you to use
subscribeOn(Schedulers.io())
which is meant for processing callbacks, amongst other things.

Related

How to use Two NotifyDataSetChanged() Atomically

To summarize my problem:
I have a list of items and a button that I click to query an API
When I click the button, two methods are called. The first method displays a progress bar, clears the list, and uses notifyDataSetChanged()
public void methodOne(){
mProgressBar.setVisibility(View.VISIBLE);
mList.clear;
mAdapter.notifyDataSetChanged();
}
The second method uses retrofit to make a query, and in the callback method, I hide the progress bar, add to the list and call notifyDataSetChanged();
public void methodTwo(){
RetrofitInterfaces.SearchForPosts service = RetrofitClientInstance.getRetrofitInstance()
.create(RetrofitInterfaces.SearchForPosts.class);
Call<Feed> call = service.listRepos(url);
call.enqueue(new Callback<Feed>() {
#Override
public void onResponse(#NonNull Call<Feed> call, #NonNull Response<Feed> response) {
try{
mProgressBar.setVisibility(View.INVISIBLE);
mList.addAll(response.body().getData());
mAdapter.notifyDataSetChanged();
} catch(Exception e){
Log.e(TAG, "Error: " + e);
}
}
#Override
public void onFailure(#NonNull Call<Feed> call, #NonNull Throwable t) {
Log.e(TAG, "onFailure: " + t);
}
});
}
}
My problem is when I call these two ones after another:
methodOne();
methodTwo();
The second method with the retrofit call sometimes returns an IndexOutOfBounds exception because methodOne() calls mList.clear() and mAdapter.notifyDataSetChanged(); while I am making edits to mList.
My question is how can I make the two happen atomically so that they don't interfere with each other?
(I want methodOne() to do everything even before the query happens in methodTwo)
You can use AsyncTask that will execute methodTwo() when methodOne() finished executing
private class MethodsTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... voids) {
methodOne();
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
methodTwo();
}
}
So instead of calling the two methods
methodOne();
methodTwo();
Use this
MethodsTask task = new MethodsTask();
task.execute();

Android Access Same instance method on activity from different threads without locking. Added synchronized method

I have a method goToNextScreen() which does a check for 3 different asynchronous process, so when all process are done the validation will changes activity (Is kind of a Splash activity)
My example code access from 3 different result callbacks to the activity's method goToNextScreen() updating the flag value for each process and to validate other flags inside.
So far this approach works but i have the next questions:
Is this approach valid? Does it have a risk for some kind of deadlock? all threads/callbacks won't collide accessing the method at the same time causing wrong validations?
class LoadingActivity extends Activity{
public boolean isFetchDone, isAnimationDone, isServiceDone, watchDog;
Presenter presenter;
protected void onCreate(#Nullable Bundle savedInstanceState) {
presenter(this);
runAnimation();
presenter.runGetXService();
runFetchFromDB();
}
//do some generic isAnimationDone
private void runAnimation(){
//animator set and animation assumed to be correct...
//...
animatorSet.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
// do anything before animation start
}
#Override
public void onAnimationEnd(Animator animation) {
isAnimationDone = true;
goToNextScreen();
}
#Override
public void onAnimationCancel(Animator animation) {
// do something when animation is cancelled (by user/ developer)
}
#Override
public void onAnimationRepeat(Animator animation) {
// do something when animation is repeating
}
});
}
//Some example function to fetch data from x DB
private void runFetchFromDB() {
final Realm realm = RealmProvider.getInstance();
final ThingDB db = new ThingDBImpl(realm);
realm.beginTransaction();
db.getData(10L)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<XData>() {
#Override
public void onCompleted() {
isFetchDone = true;
goToNextScreen();
}
#Override
public void onError(Throwable e) {
//we dont care about the result
isFetchDone = true;
goToNextScreen();
}
#Override
public void onNext(XData dataSaved) {
//Should i update isFetchDone here?
});
realm.cancelTransaction();
}
private synchronized void goToNextScreen(){
if(!watchDog) return;
if(isFetchDone && isAnimationDone && isServiceDone){
changeActivityFaded(this, SomeOtherActivity);
finish();
watchDog = true;
}
}
}
class Presenter {
Activity activity;
Presenter(Activity activity){
this.activity = activity;
}
public void runGetXService(){
new Presenter.GetServiceAsyncTask().execute();
}
private class GetServiceAsyncTask extends AsyncTask<Void, Void, Response> {
#Override
protected void onPreExecute() {
super.onPreExecute();
//do preparation for service response, etc, asumme all correct
}
#Override
protected XResponse doInBackground(Void... voids) {
try {
return //Assume correct behaviour...
} catch (NetworkConnectionException e) {
return null;
}
}
#Override
protected void onPostExecute(XResponse xResponse) {
super.onPostExecute(xResponse);
((LoadingActivity)activity).isServiceDone = true;
((LoadingActivity)activity).goToNextScreen();
}
}
}
EDIT:
I have changed the method goToNextScreen to synchronized so it supposed to not allow access from others threads at the same time. Still have doubts if withs the execution will be right.
Yes, making the method synchronized means it cannot be executed multiple times simultaneously. If a second thread calls it while it is still executing, the second thread will block until the synchronization lock is released.
https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html
So far this approach by adding the synchronized to the method goToNextScreen member of the activity instance shared on the threads accessing it has worked so far. (View the question code for solution). Although i need to add a watch dog just in case some thread pass to execute code that its supposed to execute just once.

Throw exception from Listener

I'm replacing a sync method for an async one doing the same job. The old method was throwing a custom exception if something went wrong. Now I've got a listener in the new method and I don't know how to keep throwing that exception:
#Override
protected void methodBeingChanged() throws customException {
asyncMethod(new Handler() {
#Override
public void onSuccess(String response, int responseCode) {
}
#Override
public void onError(IOException e) {
}
});
}
This exception is later being catch by another method in another class which gives feedback to the user depending on the exception message.
It is impossible. Your async task is run on another thread and your current methodBeingChanged is finish right away, which means the exception is never thrown by methodBeingChanged.
The only solution is modify the caller code, add another listener method for the specific exception
This may sound silly, but just throw it :p
https://docs.oracle.com/javase/tutorial/essential/exceptions/throwing.html
#Override
protected void methodBeingChanged() throws customException {
asyncMethod(new Handler() {
#Override
public void onSuccess(String response, int responseCode) {
}
#Override
public void onError() {
try {
throw new customException ();
}catch(customException cEx) {
// Do stuff
}
}
});
}

UI is not updated during a listener callback in android

My fragment is
public class sample extends Fragment implements statusChanger{
void onResume() {
Listener.registerListener(this);
}
void onPause() {
Listener.deRegisterListener(this);
}
#Override
public void onStatusChanged() {
MyTextView.setVisibility(View.VISIBLE);
}
}
My Interface is
public interface StatusChanger {
void onStatusChanged();
}
My callback from another java class is
public void onstatusChanged() {
listener.onStatusChanged();
}
The above is the outline of my code , I could get a call back from the ordinal java class to my fragment , but the textView is not set to visible and i do not get any runtime errors.
Looks like it might be a thread issue. What if you run the onStatusChanged() code on the UI thread?
#Override
public void onStatusChanged() {
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
MyTextView.setVisibility(View.VISIBLE);
}
});
}
In normal cases, following code will work:
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
// Update UI here.
}
});
Note: If you have wrap above code with Handler with a delay then it may not work.

SwipeRefreshLayout refresh animation doesn't stop

I've implemented a fragment which has a SwipeRefreshLayout as content view. Refresh animation is triggered at onRefresh but it never stops even if setRefreshing is set to false after retrieving the data from server.
#Override
public void onRefresh() {
handler.post(refreshing);
}
private final Runnable refreshing = new Runnable(){
public void run(){
try {
if(isRefreshing()){
handler.postDelayed(this, 1000);
}else{
swipeLayout.setRefreshing(false);
mainActivity.forceUpdate();
setLayout();
}
}
catch (Exception e) {
e.printStackTrace();
}
}
};
private boolean isRefreshing(){
return swipeLayout.isRefreshing();
}
well, of course it's not stopping to refresh, look at your loop.
if(isRefreshing()){
handler.postDelayed(this, 1000);
}else{
swipeLayout.setRefreshing(false);
mainActivity.forceUpdate();
setLayout();
}
basically you are never stopping it,
the "swipeLayout.setRefreshing(false);"
is never called, since the condition it needs is (isRefreshing()) to be false, so it doesn't make any sense, you will always get a true value from "isRefreshing()" since you're never telling it to stop.
setRefreshing to false based on the boolean value of isRefreshing is wrong in my opinion, you shouldn't rely on it, you should be the one to decide when you want to stop the refresh state.
usually PullToRefresh is used to get new data from server.
so basically I would recommend inserting this check right where you get an answer from your server :
if (swipeLayout.isRefreshing()) {
swipeLayout.setRefreshing(false);
}
because once you get your new data from the server, that's when you need to stop refreshing.
EDIT - added a full code example
protected SwipeRefreshLayout.OnRefreshListener mOnRefreshListener = new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
retrieveDocuments();
}
};
public void retrieveDocuments() {
//some code, server call
//onCompletion is your server response with a success
#Override
public void onCompletion(String result) {
if (mSwipeRefreshLayout.isRefreshing()) {
mSwipeRefreshLayout.setRefreshing(false);
}
}
}
This will remove the animation :
mSwipeRefreshLayout.setRefreshing(false);
Use setRefreshing(false) in the UI thread.
Try this:
Handler mHandler = new Handler();//In UI Thread
...
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
swipeLayout.setRefreshing(isRefreshing);
}
}, delayTime);
In my case I did
mSwipeRefreshLayout.post(new Runnable() {
#Override
public void run() {
mSwipeRefreshLayout.setRefreshing(true);
}
});
twice.
Then, it is not removed by
mSwipeRefreshLayout.setRefreshing(false);
if you want to stop loading animation when page is loaded then you have to find first that when page is completely loaded in Webview.
Here is Complete code for load a page in Webview and then stop loading animation after complete page loading.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_view);
final WebView wvSocialMediaPage = findViewById(R.id.wv_social_media_page);
wvSocialMediaPage.setWebViewClient(new CustomWebViewClient());
WebSettings webSetting = wvSocialMediaPage.getSettings();
webSetting.setJavaScriptEnabled(true);
webSetting.setDisplayZoomControls(true);
wvSocialMediaPage.loadUrl("https://facebook.com");
swipeRefreshLayout = (SwipeRefreshLayout) this.findViewById(R.id.swipe_container);
swipeRefreshLayout.setOnRefreshListener(
new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
wvSocialMediaPage.reload();
}
}
);
}
private class CustomWebViewClient extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
#Override
public void onPageFinished(WebView view, String url) {
swipeRefreshLayout.setRefreshing(false);
}
}
onPageFinished will find when page is loaded Successfully then stop animation.
Make sure your listener is set.
mSwipeRefreshLayout = findViewById(R.id.swiperefresh);
mSwipeRefreshLayout.setOnRefreshListener(this); // Important
Don't like Listeners. I do think if you are coding in c# - you have to follow its paradigms. One of those is events. MvxSwipeRefreshLayout has event called Refresh.
And what you can do in its handler is to do some stuff that takes some time, like await for it, and then set Refreshing to false
private async void SwipeToRefreshControl_Refresh(object sender, EventArgs e)
{
await ...some stuff here...
(sender as MvxSwipeRefreshLayout).Refreshing = false;
}
If you use Kotlin, you can do this with Coroutines.
Where binding.dashboardRefresh is my SwipeRefreshLayout.
//Refresh the info on Swipe-Refresh
binding.dashboardRefresh.run {
setOnRefreshListener {
menuViewModel.getFields()
val workerScope = CoroutineScope(Dispatchers.IO)
workerScope.launch {
delay(3000)
isRefreshing = false
}
}
}

Categories

Resources