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
}
}
}
Related
I have two layouts, which have views inside that have changeable content and I switch between them by making them VISIBLE and INVISIBLE
Contents of view depends on response of REST service and I have to change content of specified view when response is got.
But REST service runs on background which doesn't wait for the layout which is VISIBLE.
I am applying changes to related views within Runnable called in runOnUIThread from background Thread
It looks OK so far, but is it OK to make changes on INVISIBLE layout's child View?
Sample:
when HTTP 200 is returned from server:
public void success(retrofit.client.Response response, retrofit.client.Response ignore) {
String out = new String();
TypedInput body = response.getBody();
try {
out = getString(response);
runOnUiThread(new Runnable() {
#Override
public void run() {
txtStuCounter.setText(out);
} catch (Exception e) {
e.printStackTrace();
}
}
and TimerTask switches between views with calling function below depending on time:
private void showHideCourse(boolean show) {
if (show) {
if (isMultiple == false) {
layoutCourse.setVisibility(View.VISIBLE);
layoutMCourse.setVisibility(View.INVISIBLE);
txtStuCounter.setVisibility(View.VISIBLE);
} else {
layoutMCourse.setVisibility(View.VISIBLE);
layoutCourse.setVisibility(View.INVISIBLE);
txtStuCounter.setVisibility(View.INVISIBLE);
}
if (!noSchedule) {
//DersProgrami yururlukte
layoutSchedule.setVisibility(View.INVISIBLE);
isSchedule = false;
} else {
//Toplanti salonu videosu
logoView.stopPlayback();
wasVideoPlaying = false;
logoView.setVisibility(View.INVISIBLE);
}
}
}
use this one in xml layout
android:visibility="gone"
or use this inside your class
object.setVisibility(View.GONE);
I think Loaders are the best solution to your problem to load data in backGround
and update UI
See Example:
private LoaderManager.LoaderCallbacks<Cursor> favouriteLoaderCallbacks = new LoaderManager.LoaderCallbacks<Cursor>() {
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new AsyncTaskLoader<Cursor>(getContext()) {
#Override
protected void onStartLoading() {
forceLoad();
}
#Override
public Cursor loadInBackground() {
// do your back ground work here ...
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor mCursor) {
// set you visibilty Here...
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
}
};
then intit the loaser in onCreate Methode
getLoaderManager().initLoader(FAVOURITE_LOADER_ID, null, favouriteLoaderCallbacks);
Read More
Loaders
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.
I am working on an android application, that fetches image from Internet and show in the user interface. I am using RecyclerView for showing the image. I am planning to download the image using a separate thread. and update RecyclerView via the handler. I dont know wether this concept is correct or not, (I know AsyncTask, but for learning purpose I am trying to implement Handler.)
So I coded for the same as below
private void loadNewsThumbnailImage(ArrayList<DataItem> dataList) {
for (DataItem item : DataList) { //DataItem is the model class
loadThumbnailFromInternet(item);
}
}
private void loadThumbnailFromInternet(final DataItem dataItem) {
Thread imageDowloaderThread = new Thread(new Runnable() {
#Override
public void run() {
Bitmap bitmap = null;
try {
bitmap = getDataItemBitmap(dataItem.getmImageUrl());
dataItem.setmThumbnail(bitmap);
new Handler().post(new Runnable() { // Tried new Handler(Looper.myLopper()) also
#Override
public void run() {
mAdapter.notifyDataSetChanged();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
});
imageDowloaderThread.start();
}
I have executed this code but I am getting error, and application is terminated, I don't know why this is happening . please any one help me to sort it out. and explain what is the problem for the current code.
(Please do not suggest to use AsyncTask (I have tried that and it works fine))
UPDATE
Error getting :java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
Your application is getting terminated because you are calling notifyDataSetChanged() from a non UI Thread.
Replace:
new Handler().post(new Runnable() { // Tried new Handler(Looper.myLopper()) also
#Override
public void run() {
mAdapter.notifyDataSetChanged();
}
});
With this:
new Handler(Looper.getMainLooper()).post(new Runnable() { // Tried new Handler(Looper.myLopper()) also
#Override
public void run() {
mAdapter.notifyDataSetChanged();
}
});
The thread you defined does not have a Looper, and no message queue,so you can not send message in this thread. AsyncTask has its own Looper which you can find it in its source code. This is handler defined in AsyncTask:
private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
}
#SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
#Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. Make sure your adapter calls notifyDataSetChanged() when its content changes. [in ListView(2131296513, class xyz.ScrollDetectableListView) with Adapter(class android.widget.HeaderViewListAdapter)]
I am getting above exception sometimes while scrolling through the dynamic listview and then clicking on item.I researched a lot but unable to find the exact reason that why i am getting this error sometimes and how it can be resolved?
private ScrollDetectableListView mFListView;
public FAdapter mFAdapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_feed_view, container, false);
View headerView = getActivity().getLayoutInflater().inflate(R.layout.view_feed_header, null);
try{
mFListView = (ScrollDetectableListView) rootView.findViewById(R.id.feed_list_view);
mFContainer = (SwipeRefreshLayout) rootView.findViewById(R.id.feed_container);
mFListView.addHeaderView(headerView);
mFListView.setEmptyView(rootView.findViewById(R.id.empty_view));
mFContainer.setColorSchemeResources(R.color.green, R.color.pink, R.color.fbcolor,
R.color.instagramcolor, R.color.googlecolor, R.color.flickrcolor);
mFView = getActivity().getLayoutInflater().inflate(R.layout.view_footer, null);
ImageView rotateImageView = (ImageView) mFooterView.findViewById(R.id.spinner);
Animation rotation = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate);
rotation.setFillAfter(false);
rotateImageView.startAnimation(rotation);
mFContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh()
{
initializeFListView();
}
});
initializeFListView();
mProgressDialog.setVisibility(View.VISIBLE);
mHActivity.setDataChangedListener(new DataChangedListener() {
#Override
public void onDataChanged() {
mFContainer.setRefreshing(true);
mProgressDialog.setVisibility(View.GONE);
initializeFListView();
}
});
}catch(Exception e){}
return rootView;
}
public void initializeFListView()
{
FApi.getTrending(getActivity(), xyz, new APIResponseListener() {
#Override
public void onResponse(Object response) {
setFeedAdapter((List<Video>) response);
}
#Override
public void onError(VolleyError error) {
if (error instanceof NoConnectionError) {
String errormsg = getResources().getString(R.string.no_internet_error_msg);
Toast.makeText(getActivity(), errormsg, Toast.LENGTH_LONG).show();
}
}
});
}
private void setFAdapter(List<Video> response)
{try {
List<Video> videos = response;
mFAdapter = new FAdapter(getActivity(), videos, mProfileClickListener, mCommentClickListener);
mFListView.setOnScrollListener(new EndlessScrollListenerFeedView(getActivity(), mFListView, mFView, mFAdapter, videos, mFType, ""));
mFListView.setAdapter(mFAdapter);
mProgressDialog.setVisibility(View.GONE);
if (mFContainer.isRefreshing()) {
mFContainer.setRefreshing(false);
}
if (mFAdapter.getCount() < mCount) {
mFView.setVisibility(View.GONE);
mFListView.removeFooterView(mFooterView);
}
}catch(Exception e){}
}
}
My suggestion try to set ur list adapter on UI Thread,,,
private void setFAdapter(List<Video> response)
{
try {
List<Video> videos = response;
mFAdapter = new FAdapter(getActivity(), videos, mProfileClickListener, mCommentClickListener);
mFListView.setOnScrollListener(new EndlessScrollListenerFeedView(getActivity(), mFListView, mFView, mFAdapter, videos, mFType, ""));
runOnUiThread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
mFListView.setAdapter(mFAdapter);
}
});
mProgressDialog.setVisibility(View.GONE);
if (mFContainer.isRefreshing()) {
mFContainer.setRefreshing(false);
}
if (mFAdapter.getCount() < mCount) {
mFView.setVisibility(View.GONE);
mFListView.removeFooterView(mFooterView);
}
}catch(Exception e){}
}
}
Keep one singleton class object in hand. So that you can synchronize two thread on it. Care to be taken to not to block the ui thread.
Reduce number of interfaces to only one method to start preparing data for your list and only one method to call your notifydatasetchanged/setAdapter on list.
Means there should be only one method like prepareData() which will be executed by a background thread. synchronise this method on your singleton object.
MyListAdaper adapter = null;
// Call this from a background thread
public void prepareData() {
synchronized (SingleTonProvider.getInstance()) {
List<AnyDataTypeYouWant> data = null;
// populate data here by your application logic.
adapter = new MyListAdaper(data);
}
}
And have only one method to refresh list.
// Also Call this from a background thread only
public void refreshList() {
synchronized (SingleTonProvider.getInstance()) {
runOnUiThread(new Runnable() {
#Override
public void run() {
mFListView.setAdapter(adapter);
}
});
}
}
have no other code on any place to prepare data and set data on list.
Call the methods I mentioned from a background thread only.
I just gave general solution to your problem. You have to work on your specific case by yourself.
I am using PullToRefresh ListView from chrisbanes which I found here.
I implemented it successfully, thanks to its documentations. :)
However, I am stuck at this one point now. I am using volley to get the data from the server. It works perfectly till I added a check to see if theres no more data then simply Toast the user.
I did like below,
#Override
public void onRefresh(
PullToRefreshBase<ListView> refreshView) {
if (hasMoreData()){
//Call service when pulled to refresh
orderService();
} else{
// Call onRefreshComplete when the list has been refreshed.
toastShort("No more data to load");
orderListAdapter.notifyDataSetChanged();
mPullRefreshListView.onRefreshComplete();
}
}
The toast comes up, but I also continue seeing the Loading... message below my ListView. I thought onRefreshComplete(); should take care of it but it didn't.
How do I do this? Please help.
After banging my head for almost 3hours I was able to solve this. It was quite simple tough.
What I did was created a Handler and a Runnable which calls mPullRefreshListView.onRefreshComplete(); and checked after some time that if mPullRefreshListView was still refreshing then call the method again which closes it on the next call. :)
Code goes like this..
#Override
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
if (hasMoreData()) {
// Call service when pulled to refresh
toastShort("Last");
orderService();
} else {
// Call onRefreshComplete when the list has been
// refreshed.
toastShort("No more data to load");
upDatePull(); //this method does the trick
}
}
private void upDatePull() {
// lvOrders.setAdapter(null);
handler = new Handler();
handler.postDelayed(runnable, 1000);
}
Runnable runnable = new Runnable() {
#Override
public void run() {
mPullRefreshListView.onRefreshComplete();
if (mPullRefreshListView.isRefreshing()) {
Logger.d("xxx", "trying to hide refresh");
handler.postDelayed(this, 1000);
}
}
};
Credits to this link.
you should use onRefreshComplete(); in a separate thread like:
#Override
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
if (hasMoreData()){
//Call service when pulled to refresh
orderService();
} else{
toastShort("No more data to load");
orderListAdapter.notifyDataSetChanged();
}
new GetDataTask(refreshView).execute();
}
public class GetDataTask extends AsyncTask<Void, Void, Void> {
PullToRefreshBase<?> mRefreshedView;
public GetDataTask(PullToRefreshBase<?> refreshedView) {
mRefreshedView = refreshedView;
}
#Override
protected Void doInBackground(Void... params) {
// Do whatever You want here
return null;
}
#Override
protected void onPostExecute(Void result) {
mRefreshedView.onRefreshComplete();
super.onPostExecute(result);
}
}