I used the lifecycle callback onCreate to fetch data like below
mWeOutViewModel.getPlaceListLiveData()
.observe(this, weOutItemViewModels -> {
AppLogger.i(getCustomTag() + "adding items " + weOutItemViewModels.size());
if (weOutItemViewModels != null && weOutItemViewModels.size() > 0)
mWeOutListAdapter.addToExisting(weOutItemViewModels);
});
As you can see the AppLogger output the initial size which is 0 when the fragment is displayed, then I fetch the data and call postValue (setValue crashes the app and it expected because I fetch data from the internet using a background thread). So I call post value like below :
private void updatePlaces(List<WeOutGroupedViewModels> weOutGroupedViewModels) {
List<WeOutGroupedViewModels> oldList = placeMutableLiveData.getValue();
oldList.addAll(weOutGroupedViewModels);
AppLogger.i(TAG +" updating places "+oldList.size());
placeMutableLiveData.postValue(oldList);
}
As you can see the other AppLogger before postValue, the size of the list is displayed(not empty), but nothing happens until the app crashes and nothing is shown in the logs. I have no ways of debugging since even on debug mode nothing happens. The post value doesn't trigger the observer.
I initialize the mutableLivedata like this :
private final MutableLiveData<List<WeOutGroupedViewModels>> placeMutableLiveData = new MutableLiveData<>();
and access like this :
public LiveData<List<WeOutGroupedViewModels>> getPlaceListLiveData() {
return placeMutableLiveData;
}
Event when I make the livedata public to access directly the livedata, there is no change (just in case someone thinks that's is where the issue comes from)
Instead of placeMutableLiveData.postValue(oldList);
I recommend using
placeMutableLiveData.postValue(Collections.unmodifiableList(new ArrayList<>(newList));
That way, the next time you access this list, you won't be able to mutate it in place, which is a good thing. You're not supposed to mutate the list inside a reactive state holder (MutableLiveData).
So theoretically it should look like this:
private void updatePlaces(List<WeOutGroupedViewModels> weOutGroupedViewModels) {
List<WeOutGroupedViewModels> newList = new ArrayList<>(placeMutableLiveData.getValue());
newList.addAll(weOutGroupedViewModels);
AppLogger.i(TAG +" updating places "+newList.size());
placeMutableLiveData.postValue(Collections.unmodifiableList(newList));
}
I have displayed some data in a Listview using ListAdapter. Now I want the listview to refresh automatically after x seconds. How can this be done?
I am using AsyncTask to fetch data from Server.
I have tried this:
public void reloadDeviceData() {
handler.postDelayed(runnable = new Runnable() {
public void run() {
new GetDevices().execute(); // this method will contain your almost-finished HTTP calls
handler.postDelayed(this, TIME);
}
}, TIME);
}
What is happening is that the listview does not refresh but instead add items after the current listview.
GetDevices is the class which extends AsyncTask here.
When you get the response from API. just update the adapter list by using follwing line adapter.list.clear() then after adapter.list.addAll(apiList) and then after You just have to notify the listview adapter.
adapter.notifydatasetchanged();
I would recommend :
use recyclerview for performance point of view .
Check Delta difference between new and old list and add /update those items only. For difference you can use either basic collections class methods or DiffUittls lib
I am getting data from server and then parsing it and storing it in a List. I am using this list for the RecyclerView's adapter. I am using Fragments.
I am using a Nexus 5 with KitKat. I am using support library for this. Will this make a difference?
Here is my code: (Using dummy data for the question)
Member Variables:
List<Business> mBusinesses = new ArrayList<Business>();
RecyclerView recyclerView;
RecyclerView.LayoutManager mLayoutManager;
BusinessAdapter mBusinessAdapter;
My onCreateView():
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Getting data from server
getBusinessesDataFromServer();
View view = inflater.inflate(R.layout.fragment_business_list,
container, false);
recyclerView = (RecyclerView) view
.findViewById(R.id.business_recycler_view);
recyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(mLayoutManager);
mBusinessAdapter = new BusinessAdapter(mBusinesses);
recyclerView.setAdapter(mBusinessAdapter);
return view;
}
After getting data from server, parseResponse() is called.
protected void parseResponse(JSONArray response, String url) {
// insert dummy data for demo
mBusinesses.clear();
Business business;
business = new Business();
business.setName("Google");
business.setDescription("Google HeadQuaters");
mBusinesses.add(business);
business = new Business();
business.setName("Yahoo");
business.setDescription("Yahoo HeadQuaters");
mBusinesses.add(business);
business = new Business();
business.setName("Microsoft");
business.setDescription("Microsoft HeadQuaters");
mBusinesses.add(business);
Log.d(Const.DEBUG, "Dummy Data Inserted\nBusinesses Length: "
+ mBusinesses.size());
mBusinessAdapter = new BusinessAdapter(mBusinesses);
mBusinessAdapter.notifyDataSetChanged();
}
My BusinessAdapter:
public class BusinessAdapter extends
RecyclerView.Adapter<BusinessAdapter.ViewHolder> {
private List<Business> mBusinesses = new ArrayList<Business>();
// Provide a reference to the type of views that you are using
// (custom viewholder)
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView mTextViewName;
public TextView mTextViewDescription;
public ImageView mImageViewLogo;
public ViewHolder(View v) {
super(v);
mTextViewName = (TextView) v
.findViewById(R.id.textView_company_name);
mTextViewDescription = (TextView) v
.findViewById(R.id.textView_company_description);
mImageViewLogo = (ImageView) v
.findViewById(R.id.imageView_company_logo);
}
}
// Provide a suitable constructor (depends on the kind of dataset)
public BusinessAdapter(List<Business> myBusinesses) {
Log.d(Const.DEBUG, "BusinessAdapter -> constructor");
mBusinesses = myBusinesses;
}
// Create new views (invoked by the layout manager)
#Override
public BusinessAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
Log.d(Const.DEBUG, "BusinessAdapter -> onCreateViewHolder()");
// create a new view
View v = LayoutInflater.from(parent.getContext()).inflate(
R.layout.item_business_list, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
Log.d(Const.DEBUG, "BusinessAdapter -> onBindViewHolder()");
Business item = mBusinesses.get(position);
holder.mTextViewName.setText(item.getName());
holder.mTextViewDescription.setText(item.getDescription());
holder.mImageViewLogo.setImageResource(R.drawable.ic_launcher);
}
// Return the size of your dataset (invoked by the layout manager)
#Override
public int getItemCount() {
Log.d(Const.DEBUG, "BusinessAdapter -> getItemCount()");
if (mBusinesses != null) {
Log.d(Const.DEBUG, "mBusinesses Count: " + mBusinesses.size());
return mBusinesses.size();
}
return 0;
}
}
But I don't get the data displayed in the view. What am I doing wrong?
Here is my log,
07-14 21:15:35.669: D/xxx(2259): Dummy Data Inserted
07-14 21:15:35.669: D/xxx(2259): Businesses Length: 3
07-14 21:26:26.969: D/xxx(2732): BusinessAdapter -> constructor
I don't get any logs after this. Shouldn't getItemCount() in adapter should be called again?
In your parseResponse() you are creating a new instance of the BusinessAdapter class, but you aren't actually using it anywhere, so your RecyclerView doesn't know the new instance exists.
You either need to:
Call recyclerView.setAdapter(mBusinessAdapter) again to update the RecyclerView's adapter reference to point to your new one
Or just remove mBusinessAdapter = new BusinessAdapter(mBusinesses); to continue using the existing adapter. Since you haven't changed the mBusinesses reference, the adapter will still use that array list and should update correctly when you call notifyDataSetChanged().
Try this method:
List<Business> mBusinesses2 = mBusinesses;
mBusinesses.clear();
mBusinesses.addAll(mBusinesses2);
//and do the notification
a little time consuming, but it should work.
Just to complement the other answers as I don't think anyone mentioned this here: notifyDataSetChanged() should be executed on the main thread (other notify<Something> methods of RecyclerView.Adapter as well, of course)
From what I gather, since you have the parsing procedures and the call to notifyDataSetChanged() in the same block, either you're calling it from a worker thread, or you're doing JSON parsing on main thread (which is also a no-no as I'm sure you know). So the proper way would be:
protected void parseResponse(JSONArray response, String url) {
// insert dummy data for demo
// <yadda yadda yadda>
mBusinessAdapter = new BusinessAdapter(mBusinesses);
// or just use recyclerView.post() or [Fragment]getView().post()
// instead, but make sure views haven't been destroyed while you were
// parsing
new Handler(Looper.getMainLooper()).post(new Runnable() {
public void run() {
mBusinessAdapter.notifyDataSetChanged();
}
});
}
PS Weird thing is, I don't think you get any indications about the main thread thing from either IDE or run-time logs. This is just from my personal observations: if I do call notifyDataSetChanged() from a worker thread, I don't get the obligatory Only the original thread that created a view hierarchy can touch its views message or anything like that - it just fails silently (and in my case one off-main-thread call can even prevent succeeding main-thread calls from functioning properly, probably because of some kind of race condition)
Moreover, neither the RecyclerView.Adapter api reference nor the relevant official dev guide explicitly mention the main thread requirement at the moment (the moment is 2017) and none of the Android Studio lint inspection rules seem to concern this issue either.
But, here is an explanation of this by the author himself
I had same problem. I just solved it with declaring adapter public before onCreate of class.
PostAdapter postAdapter;
after that
postAdapter = new PostAdapter(getActivity(), posts);
recList.setAdapter(postAdapter);
at last I have called:
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
// Display the size of your ArrayList
Log.i("TAG", "Size : " + posts.size());
progressBar.setVisibility(View.GONE);
postAdapter.notifyDataSetChanged();
}
May this will helps you.
Although it is a bit strange, but the notifyDataSetChanged does not really work without setting new values to adapter. So, you should do:
array = getNewItems();
((MyAdapter) mAdapter).setValues(array); // pass the new list to adapter !!!
mAdapter.notifyDataSetChanged();
This has worked for me.
Clear your old viewmodel and set the new data to the adapter and call notifyDataSetChanged()
In my case, force run #notifyDataSetChanged in main ui thread will fix
public void refresh() {
clearSelection();
// notifyDataSetChanged must run in main ui thread, if run in not ui thread, it will not update until manually scroll recyclerview
((Activity) ctx).runOnUiThread(new Runnable() {
#Override
public void run() {
adapter.notifyDataSetChanged();
}
});
}
I always have this problem that I forget that the RecyclerView expects a new instance of a List each time you feed the adapter.
List<X> deReferenced = new ArrayList(myList);
adapter.submitList(deReferenced);
Having the "same" List (reference) means not declaring "new" even if the List size changes, because the changes performed to the List also propagates to other Lists (when they are simply declared as this.localOtherList = myList) emphasis on the keyword being "=", usually components that compare collections make a copy of the result after the fact and store it as "old", but not Android DiffUtil.
So, if a component of yours is giving the same List each and every time you submit it, the RecyclerView won't trigger a new layout pass.
The reason is that... AFAIR, before the DiffUtil even attempts to apply the Mayers algorithm, there is a line doing a:
if (newList == mList)) {return;}
I am not sure how much "good practice" does de-referencing within the same system is actually defined as "good" ...
Specially since a diff algorithm is expected to have a new(revised) vs old(original) component which SHOULD in theory dereference the collection by itself after the process has ended but... who knows...?
But wait, there is more...
doing new ArrayList() dereferences the List, BUT for some reason Oracle decided that they should make a second "ArrayList" with the same name but a different functionality.
This ArrayList is within the Arrays class.
/**
* Returns a fixed-size list backed by the specified array. (Changes to
* the returned list "write through" to the array.) This method acts
* as bridge between array-based and collection-based APIs, in
* combination with {#link Collection#toArray}. The returned list is
* serializable and implements {#link RandomAccess}.
*
* <p>This method also provides a convenient way to create a fixed-size
* list initialized to contain several elements:
* <pre>
* List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
* </pre>
*
* #param <T> the class of the objects in the array
* #param a the array by which the list will be backed
* #return a list view of the specified array
*/
#SafeVarargs
#SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a); //Here
}
This write-through is funny because if you:
Integer[] localInts = new Integer[]{1, 2, 8};
Consumer<List<Integer>> intObserver;
public void getInts(Consumer<List<Integer>> intObserver) {
this.intObserver = intObserver;
dispatch();
}
private void dispatch() {
List<Integer> myIntegers = Arrays.asList(localInts);
intObserver.accept(myIntegers);
}
... later:
getInts(
myInts -> {
adapter.submitList(myInts); //myInts = [1, 2, 8]
}
);
Not only does the List dispatched obeys the dereferencing on each submission, but when the localInts variable is altered,
public void set(int index, Integer value) {
localInts[index] = value;
dispatch(); // dispatch again
}
...
myModel.set(1, 4) // localInts = [1, 4, 8]
this alteration is also passed to the List WITHIN the RecyclerView, this means that on the next submission, the (newList == mList) will return "false" allowing the DiffUtils to trigger the Mayers algorithm, BUT the areContentsTheSame(#NonNull T oldItem, #NonNull T newItem) callback from the ItemCallback<T> interface will throw a "true" when reaching index 1. basically, saying "the index 1 inside RecyclerView (that was supposed to be 2 in th previous version) was always 4", and a layout pass will still not perform.
So, the way to go in this case is:
List<Integer> trulyDereferenced = new ArrayList<>(Arrays.asList(localInts));
adapter.submitList(trulyDereferenced);
Ok, so ive spend two days on this and I have tried everything under the sun and Google :L
Basically, if I add or delete something from my server, I want to update my listview which is in mylistfragment from my detailfragment to reflect the changes on the server. The changes have occur ed through clicking either a delete or add button in my detail fragment.
I have a callback method which has everything that I need to repopulate the listview with I just cant seem to get the listview to update.
Any help would be much appreciated.
My callback code is as follows :
public final Handler myCallBackAll = new Handler() {
#Override
public void handleMessage(Message msg) {
ListView lv = (ListView) view.findViewById(R.id.listView1);
if (msg.obj == "No Response") {
TextView tv2 = (TextView) view.findViewById(R.id.textView2);
tv2.setText("No Response. Please check your internet connection");
tv2.setVisibility(View.VISIBLE);
lv.setVisibility(View.INVISIBLE);
} else {
Beer beers = new Beer();
ArrayAdapter<Beer> arrayAdapter = new ArrayAdapter<Beer>(
getActivity(), android.R.layout.simple_list_item_1,(List)msg.obj);
lv.setAdapter(arrayAdapterNew);
arrayAdapter.notifyDataSetChanged();
}
}
};
It doesnt throw any errors just doesnt work
How my callback is called is here
if (isBound) {
myBinder.getAllBeer(myCallBackAll);
}
Its a bounded service I have to use, a stipulation of the project. All this code works, just mylistview wont update
You say that you have already have the callback set up, so I am assuming the callback is being fired accordingly. If that is the case, make sure you update the data source (Array, List, etc) you are using in your adapter to reflect the changes you made in detailfragment.
Also, don't forget that every time you add, edit, delete data from your adapter's data source, you must call mAdapter.notifyDataSetChanged() to tell the adapter something in your data changed.
EDIT
Try creating your handler this way instead: public final Handler myCallBackAll = new Handler(Looper.getMainLooper())
notifyDataSetChanged() must be executed on the main UI thread. When you create a new handler it will execute on its own thread, so you need to make sure it does execute on the UI thread. Take a look at here for more information
finally figured it out, may not be the best approach but it does work, i had declare my listview as a static variable in my main activity, i then reference this variable in both fragments and it works a treat :P
I have standard ListView activity linked to the database:
dbHelper = new DbAdapter(this);
dbHelper.open();
recordsCursor = dbHelper.fetchAllRecords();
startManagingCursor(recordsCursor);
String[] from = new String[]{DbAdapter.KEY_1, DbAdapter.KEY_2};
int[] to = new int[]{R.id.text1, R.id.background};
adapter = new SimpleCursorAdapter(this, R.layout.row, recordsCursor, from, to);
User has an option to update the list manually thru the menu item. It runs the task to get updated date from the server:
new SyncTask(getApplicationContext(), true).execute();
(second parameter indicates if this is manually started synchronization or automatic)
Once synchronization finished (onPostExecute), I would like to update the list (fetchAllRecords, changeCursor etc.) if the same activity is still displayed. How could I understand if this is the case?
Unlike fragments you don't have any methods to check the activity lifecycle, but it's simple to add something similar:
private boolean mResumed;
#Override
protected void onResume() {
mResumed = true;
}
#Override
protected void onPause() {
mResumed = false;
}
private void isResumed() {
return mResumed;
}
Simply call isResumed() when your background task completes to see if the Activity is still visible on the screen.
Well, i don't understand in deep the main problem.
You say before:
" what if user just chosen a item in menu item to refresh the list and then waits for its completion? "
If this happens you can update the listview at the end of the task like you say before "onPostExecute" (Without a problem).
But in the other case you can retrive the state of the activity with methods that return flags i you want or if your using fragments you can getActivity() to do something. Ever when the Activity will be displayed the activity do a 'Resumen' step so like Philio explains before.
Conclusion, you can update the listview automatic in onResumen and you can update it manually in onPostExecute in the syncTask. This method will be called in automatic or manual mode doesn't matter.