I know there are a tonne of answers on SO for this but I can't seem to get it working how I'd like. I have a SherlockFragment containing a ListView which is populated by some data retrieving from a file. I've added a refresh button to the Fragment, the idea being that when you press it it retrieves any changes in the file and adds them to the view if necessary. I also added the popular PullToRefresh library.
Unfortunately, nothing changes however when I reload the Fragment (for example, rotating the device) I can see the new data. I've read about notifyDataSetChanged() but it isn't working for me. The only thing I've gotten working is calling mPager.notifyDataSetChanged() from my main FragmentActivity class and having the following set in my ViewPager adapter:
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
This accomplishes what I want, but it's not very sleek about it, and you can see it forcing the reload as it were. I'm currently using PullToRefresh and it forces it off page abruptly and quite frankly it just looks bad. To put it bluntly I need to call onCreateView of my Fragment from my AsyncTask reloading the data.
If needed I'll post code, there's just a lot of it so I wouldn't want to post anything unneeded.
Oh, and please note I tried notifyDataSetChanged() in the onPostExecute() of my task.
Thanks for any guidance.
I found a way around this but it's not perfect, so I won't accept this in case something better comes along.
Because upon return to my Fragment onResume() is called (this answer is also pretty specific to my project), I just did the following:
#Override
public void onResume(){
super.onResume();
// Make the adapter again
adapter = new FeedListAdapter(this, feed);
// Set it to the list again
list.setAdapter(adapter);
}
This refreshes the list (badly) and is still fairly noticeable, although if I use a button instead of PullToRefresh it isn't.
My solution (however bad) alongside PullToRefresh is to stick it in a handler with a delayed trigger to let the pulled down "refresh" section disappear before it runs.
#Override
public void onResume(){
super.onResume();
new Handler().postDelayed(new Runnable() {
public void run() {
adapter = new FeedListAdapter(this, feed);
list.setAdapter(adapter);
}
}, 500);
}
Again, this is pretty specific to my project and a very strange way of doing it, so any "perfect" answer please share :)
EDIT: There was still an issue with it being a bit jumpy with PullToRefresh, so my solution was to wait in a new thread until the PullToRefresh is hidden again, then rebuild the list (it's messy, but it works so whatever):
#Override
public void onResume(){
super.onResume();
// Start a new thread
new Thread(new Runnable() {
#Override
public void run() {
try {
// Wait until the PullToRefresh is hidden
synchronized(this){
wait(190);
}
} catch(InterruptedException ex) { }
// Post a new runnable
threadHandler.post(new Runnable(){
public void run(){
// Recreate the adapter from the new feed
adapter = new FeedListAdapter(FeedListActivity.this, feed);
// Set the recreated adapter
list.setAdapter(adapter);
}
});
}
}).start();
}
}
EDIT 2:
Just noticed I missed something obvious. I just changed my list.onRefreshComplete(); for the PullToRefresh view to my onResume() and the jumpiness was taken care of. Still, I think the above solution is more impressive :p
So my code:
#Override
public void onResume(){
super.onResume();
// Recreate the adapter from the new feed
adapter = new FeedListAdapter(this, feed);
// Set the recreated adapter
list.setAdapter(adapter);
// The list has finished refreshing
list.onRefreshComplete();
}
Related
RecyclerView calls onCreateViewHolder a bunch of times and then just keeps binding the data to these views. My view creation is slightly expensive and hence I need to defer rest of the UI tasks until my RecyclerView is done creating all the views.
I tried adding a ViewTreeObserver.OnGlobalLayoutListener but this callback gets called before even the first onCreateViewHolder() call.
Any idea how do I go about it?
After some research I've found out a solution with Handler. As you I'm looking for a beautiful code and this is a bit messy for me. But works perfectly anyway.
Handler is a class that you can use in a way to post message and/or Runnable, which will be added in a queue, then executed when that queue is finished.
My plan is, given that the adapter works on the UI, (inflate ect...) the creation and initialization (all onCreateViewHolder and onBindViewHolder) are added at a moment in the handler of the main thread.
That means that if you post a message in the main thread queue (the same obligatory used by your adapter), then the message will be executed after any previous request (after your adapted has finished to initialize everything).
Exemple :
Main activity
Initialization of the handler :
private Handler mHandler;
#Override
protected void onCreate(Bundle iSavedInstanceState) {
...
mHandler = new Handler(Looper.getMainLooper());
}
Initialization of your CustomAdapter :
private void initializeAdapter(...) {
MyCustomAdapter lMyNewAdapter = new MyCustomAdapter(...)
...
lNewAdapter.SetOnFirstViewHolderCreation(new
MyCustomAdapter.OnFirstViewHolderCreation {
#Override
public void onCreation() {
mHandler.post(new Runnable() {
#Override
public void run() {
// Finally here, the code you want to execute
// At the end of any Create and Bind VH of your
// Adapter
}
});
}
});
}
MyCustomAdapter
private boolean mIsViewHolderCreationStarted;
private OnFirstViewHolderCreation mOnFirstViewHolderCreation;
public CustomItemViewAdapter onCreateViewHolder(
#NonNull ViewGroup iViewGroup, int iI) {
...
if (!mIsViewHolderCreationStarted) {
mIsViewHolderCreationStarted = true;
if (mOnFirstViewHolderCreation != null) {
// It's at this point that we want to add a new request
// in the handler. When we're sure the request of the
// adapter has begun.
mOnFirstViewHolderCreation.onCreation();
}
}
}
public void setOnFirstViewHolderCreation(OnFirstViewHolderCreation iAction) {
mOnFirstViewHolderCreation = iAction;
}
public interface OnFirstViewHolderCreation {
void onCreation();
}
Note
Be aware that this solution will execute a code at the end of the first initialization of the enteer page that it is possible to show in a case of a RecyclerView.
A onCreateViewHolder might be called in case the screen is scrolled.
Which means that this solution does not guarantee you this handler message is executed after all possible onCreateViewHolder.
It only helps you to avoid an overload on the MainThread, during the greedy work of the adapter init.
Something else, in case you're using animations with your adapter to make it appears smoothly or something else (one of the good reasons to use this way to do), don't forget to put your RecyclerView in VISIBLE and not GONE, otherwise, the initialization of the adapter never happens.
My situation is the following: I have a RecyclerView in which I want to insert data.
I add data one by one until the RecyclerView is full. The data comes from a web service.
This is the code I use:
#Override
public void receive(Response response) {
_adapter.add(response.getData());
new Handler().post(new Runnable() {
#Override
public void run() {
fetchIfNotFull();
}
});
}
private void fetchIfNotFull() {
if (_layoutManager.findLastVisibleItemPosition() == _layoutManager.getItemCount() - 1)
fetchData(); // this will call receive(Response) when it's done
}
The problem is that, when I run the application, the RecyclerView is not filled, like I expect (but sometimes it does!).
I found out that _layoutManager.findLastVisibleItemPosition() does not always return the correct value (the one I expect at least), whereas _layoutManager.getItemCount() does, so no more data are fetched...
I thought that wrapping the call inside the Handler would help, so it would be called after the next layout update, but it didn't do the trick.
And here is the strange thing: If I call handler.postDelayed() with 1000 milliseconds, it works fine! (I didn't try other values), because the layout was updated after that time. But I don't like this solution (hack). Is there a way to make sure that the LayoutManager has been updated?
After the line
_adapter.add(response.getData());
add this one
_adapter.notifyItemChanged(_adapter.getItemCount() - 1);
I am a newbie here, and I have searched online (and on stackoverflow) for the answer but I am still struggling to make it work.
What I am trying to do, is (1) update my list with fresh "posts" using an asyncronous task - thus allowing the user to continue using the app as I download new posts to their android.
I am struggling to get the updating task to work, and it could be tied to an obvious implementation problem (or not).
What I have is (1) A customized list (it allows "pull to refresh" - but as stated it is a problem that it is not refreshing any data at this point). (2) a custom AsyncTask that will theoretically populate more posts for the main list on the main screen.
Here is problem point in the list:
listView.setOnRefreshListener(new PullToRefreshListView.OnRefreshListener(listView) {
#Override
public void onRefresh() {
mylistAdapter.loadNewData(); //loads new data <-------------------
m_ptrlistView.postDelayed(new Runnable() {
#Override
public void run() {
m_ptrlistView.onRefreshComplete();
}
}, 2000);
////////////
Here is the function "loadNewData" in the adapter
public void loadNewData(PullToRefreshListView List){
//load new stuff
new AsyncFetchMore(list).execute();
// MANDATORY: Notify that the data has changed
notifyDataSetChanged();
return;
}
And... my asyncTask that is extended's implementation
#Override
protected Object doInBackground() {
try {
//simulating a long task
Thread.sleep(5000);
} catch (InterruptedException e) {
Log.d("Notes", "Thread failed to sleep");
}
//create dummy posts for testing
for ( int i = 12; i < 24; i++ ) {
Post pNewPost = new Post();
pNewPost.setText("POST # " + i);
m_alNewPosts.add(pNewPost);
}
return null;
}
///////////////////////////////
Perhaps I'm approaching it all wrong. I'm having doubts... But I just don't know what the right next step is and I am pretty lost here! Can you give me any tips?
It looks like you arent doint anything with results that are returned by your async task. But before you go to fix it, please consider using Loaders - they are asyncronous and made specifically for your purpouse - getting data for Fragment/Activity. Here is documentation for them. It looks like you might have a fair amount of refactoring to do if you want to implement them, but its well worth it - its the right way. hope this helps.
You are calling notifyDataSetChanged() right after you start your Async thread, which hasn't populated your data. Call notifyDataSetChanged() after the data has been updated. Check out the examples in ApiDemos in the samples directory of the SDK.
I have a very simple ListView in my andriod app. However, everytime I call notifyDataSetChanged() on my adapter, it 'blinks'. It works fine, but the ListView flashes. Like goes compeltely white, and then comes back in a fraction of a second. What may be causing this?
I am not sure what code to share as this is a very simple ListView / adapter but if you'd like to see a certain part of the code please let me know.
This is the code that updates the listView:
ParseCloud.callFunctionInBackground("recentWithAds", new HashMap<String, Object>(), new FunctionCallback() {
public void done(Object resp, ParseException e) {
mLoading = false;
mSwipeLayout.setRefreshing(false);
mArticles.clear();
mArticles.addAll(articles);
mAdapter.notifyDataSetChanged();
}
});
Thanks
I'm having a problem refresh the data in list view.
I get the data in the list from a server, and when I want to refresh the data I need to go to the server and receive the new data.
the notifyDataSetChanged() not helping and also the ListView.invalidateViews not helping.
when I rotate the device the list updated.
how can I load the list view in the same way the screen rotation do it?
This is the code on create that fill the list view.
thanks in advance.
query = new ParseQuery(PET_CLASS_NAME);
petListView.addHeaderView((View)getLayoutInflater().inflate(R.layout.header_row, null));
petDetailIntent = new Intent(getApplicationContext(), PetDetailActivity.class);
selectCityIntent = new Intent(this, CitiesActivity.class);
loadingIntent = new Intent(getApplicationContext(), LoadingActivity.class);
startActivityForResult(loadingIntent, LOADING_INTENT_CODE);
/*the user see list of pets that are still missing*/
query.whereEqualTo(PET_FOUNDED, false);
selectedCity = settings.getString("cityQuery", "");
if(selectedCity != ""){
query.whereEqualTo(PET_CITY, selectedCity);
}
query.findInBackground(new FindCallback() {
#Override
public void done(List<ParseObject> list, ParseException e) {
if (e == null) { //objects retrieved well
petList.addAll(list);
//MyAdapter
adapter = new MyAdapter(
getApplicationContext(),
android.R.layout.simple_list_item_1,
R.id.tv_pet_name,
petList);
setListAdapter(adapter);
}
else{
toaster(getResources().getString(R.string.error_message_load_pets));
finish();
}
finishActivity(LOADING_INTENT_CODE);
}
});
Use a AsyncTask for loadData from Server. It will load it faster.
Try this out:
private class YourTask extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... s) {
//Here you have to make the loading / parsing tasks
//Don't call any UI actions here. For example a Toast.show() this will couse Exceptions
// UI stuff you have to make in onPostExecute method
}
#Override
protected void onPreExecute() {
// This method will called during doInBackground is in process
// Here you can for example show a ProgressDialog
}
#Override
protected void onPostExecute(Long result) {
// onPostExecute is called when doInBackground finished
**// Here you can for example fill your Listview with the content loaded in doInBackground method**
}
}
And than you just have to call this AsyncTask always if you loading content from your server:
new YourTask().execute("");
Try it out!.. Hope this helps..
When you rotate the device, the activity is actually started stopped and started and your initial request will be made again.
You should place your request code into a method and recall it yourself
Similar to what Rawkode mentioned, it doesn't seem like the code that actually does the work of retrieving data from the server is reusable (since it lives in onCreate()). Take a look at this diagram: http://developer.android.com/images/activity_lifecycle.png. As you can can see, the onCreate() method only gets executed once, unless the Activity is re-created (i.e. rotating screen).
Also, from the given code, there doesn't seem to be evidence of a refresh method either. How will users be able to refresh the data? Consider refactoring your code such that the work is done in a method that you can call later on (i.e. refreshData()) and then figure out a way in which you would like your users to refresh. For example, you can use either the ActionBar with a refresh ActionItem, or a menu option or even a button.