I've a vertical RecyclerView and each element of it, is a nested horizontal RecyclerView. Both have their Adapter and ViewHolder. When I change a flag, I want to be able to refresh the drawing of all items in each inner horizontal RecyclerView. I've written a method in the outer adapter that consequently call the inner one:
OuterAdapter:
public void setEditEnabled(boolean enabled) {
innerAdapter.setEditEnabled(enabled);
notifyDataSetChanged();
}
InnerAdapter:
public void setEditEnabled(boolean enabled) {
this.editable = enabled;
notifyDataSetChanged();
}
Then in the activity I call:
outerAdapter.setEditEnabled(editable);
outerRecyclerView.invalidate();
But only some "rows" are correctly updated...How can I solve this?
EDIT: so the flow is:
Outer setEditEnabled -> inner setEditEnabled -> inner notify -> outer notify
First of all, why are you calling invalidate on the RecyclerView. when you call notifyDataSetChanged() on the adapter, it automatically updates the RecyclerView items. And why is there only one inner Adapter. I think there should be a different adapter for each horizontal RecyclerView.
I don't know if I'm doing it right, but it seems to work fine.
Previously I was instantiating a new innerAdapter in the ViewHolder constructor:
class ViewHolder extends RecyclerView.ViewHolder {
RecyclerView innerRecyclerView;
ViewHolder(View view) {
super(view);
[...] // init things
innerRecyclerView.setAdapter(new InnerAdapter(context, onItemClickListener));
}
}
Now I'm doing the new in the OuterAdapter constructor and saving the reference as private field of the class:
public DailyMenusAdapter(Context context, OnItemClickListener onItemClickListener) {
innerAdapter = new InnerAdapter(context, onItemClickListener);
}
Then I pass the reference to:
innerRecyclerView.setAdapter(innerAdapter);
Is that ok in your opinion? So reusing the same adapter over and over again.
You know, I think if you will create annonimous Adapter class instead of notifyDataSetChanged (somethink like mRecyclerView.setAdapter(new YourAdapter(this, listOfDataYouPass))) it will work fine.
EDIT:Usually we do something like this to set adapter to RecyclerView:
List mLogs = new ArrayList<>;
mLogs.addAll (logs);
mLogAdapter = new LogAdapter(this, mLogs);
mRecyclerView = findViewById(R.id.log_list);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(mLogAdapter);
and then, when we need to modify something we call mLogAdapter.notifyDataSetChanged. Or just call notifyDataSetChanged inside Adapter class. But in many cases it works wrong, hard to say why in your case.
So here is what I advise:
instead of call notifyDataSetChanged make this method and call it when you need to update or change something:
private void updateRecyclerView (){
mRecyclerView.setAdapter(new LogAdapter(this, mLogs));
}
Note: Do not pay attention to the names, I took code from the first project I got
Related
I'm trying to attach an onClickListener() method to an item which is inside a Recycler view. I know I can easily achive that by doing it from the RecyclerAdapter, but the goal of doing that is to show a custom dialog with some information that parent fragment contains, there are some ways to pass data, but I think that's better to attach the listener from fragment instead, and this way I can directly access the data.
I've tried to access from the fragment the way I use to do it from the adapter, with some modifications:
myRecyclerAdapter.myViewHolder.reportContainer.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(getContext(),"Touch",Toast.LENGTH_SHORT).show();
}
});
But aparently the myViewHolder object it's not created yet by the time I try to use it, so I get the Java NullPointerException (F..$&##^$&^%, don't misunderstand me, I love it).
So, I need some help to do what I'm trying to, or some other good ideas to try, warning: I;m really trying to avoid passing data, except with maybe a ViewModel (don't know if I can), becouse it's a lot of fields to pass
This is fundamentally incorrect. The problem here is, there are multiple ViewHolders in the RecylerView. Which one do you want to attach it to? There would be n number of items and not all items will be rendered at the same time.
Instead of updating the ViewHolder, use a callback.
class MyAdapter extends RecyclerView.Adapter {
MyAdapterCallback callback = null;
....
#Override
public void onBindViewHolder(holder: ViewHolder, position: Int) {
holder.reportContainer.setOnClickListener { // You can set this in OnCreateViewHolder as well.
if (callback != null) {
callback.onClick();
}
}
}
}
interface MyAdapterCallback {
void onClick()
}
From your fragment,
myAdapter.callback = new MyAdapterCallback() {
#Override
public void onClick() {
// Access your fragment variables here.
}
}
In my activity I have one recyclerview and each item view contains buttons. I want to be able to change some UI elements and other things such as an Array of custom objects for the adapter itself in my activity from the recyclerview adapter. Until now I declared the needed views as static but I found out that it's a terrible practice.
Example: I have the following recyclerview that represents a cart filled with a custom viewholder, from an Array of custom "cart_product" objects. (One of this custom oject's proprieties is "quantity" - represented by the spinner). I want to be able to change the object's "quantity" property by changing the spinner's value from the adapter... How could this be done? And when all the products are removed from cart (by swiping & detected from adapter) I want to display a textvie
ScreenShot
You can use callbacks:
In adapter create an interface:
public interface EventHandler {
void handle(int position) // if u need know position. If no, just create method without params
}
Create an private instance of interface in adapter:
public class YourAdapter extends RecyclerView.Adapter<YourHolder> {
private EventHanlder handler;
}
Implement EventHanlder in activity:
public class Mainacitivity extends Activity implements YourAdapter.EventHandler {
//.....
#Override
void handle (int position) {
// TODO do whatever u want
}
}
Add EventHandler to constructor parameters:
public YourAdapter (List<YourObject> data, EventHandler handler) {
//....
this.handler = handler;
}
When you need to change UI call
handler.hanlde(position);
And, finally pass this when initializing adapter
adapter = new YourAdapter (data, this)
If u need something else (not position), just change signature of handle() method
I have a ListView that displays a list of states. Whenever the user selects a state, I will load a list of cities that are in this state. For this, I have a manager that, whenever an item is selected, will replace the underlying list that my custom BaseAdapter reads with the item's children.
The problem is, this works fine, but the items are just replaced on the spot, I'd really want a nice transition for this, that is, the list of states moving to the left and being replaced by the list of cities coming from the other edge
Is there a class or method that could help me animate that? The only option I've thought is to keep two different Listview and fade the first one out while the other one fades in. Not the most elegant solution, I was wondering if this could be achieved somehow with only one ListView
I'm not sure how to do this with ListView, but I know you could use a RecyclerView and add animations in the adapter's onBindViewHolder method.
This method is called once for every visible item when you call notifyDataSetChanged().
Edit:
Something like this:
Some Class:
RecyclerView recyclerv;
MyAdapter mAdapter;
LinearLayoutManager mLayoutManager;
List<String> mDataset;
//instantiate recyclerview, adapter, layoutmanager, dataset. populate dataset.
recyclerv.setDataset(mDataset);
MyAdapter.java:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private List<String> dataset;
public MyAdapter(){}
//extend ViewHolder class here
public class ViewHolder extends RecyclerView.Holder { ... }
//implement onCreateViewHolder
/*implement onBindViewHolder.
*animate your view here, but be sure to add an animation count
else the items will keep getting animated everytime a new item appears */
//implement getItemCount
public void setDataset(List<String> dataset) {
this.dataset = dataset;
notifyDataSetChanged(); //this will trigger onBindViewHolder for every visible item
}
}
I have implemented my RecyclerView and even added an onscrolllistener to support infinity scrolling and now I'm stuck with a, hopefully, easy problem: How can I add the newly loaded data to the existing dataset?
My current approach: I create a new array with the length of the existing dataset + the length of the newly loaded data. I System.arraycopy my existing dataset and add the new content with a for-loop.
This works but the list is always reset (scrolls back to the top) and I assume my way to add additional content is overly complicated/wrong, though the tutorials I have looked at seem to pass over this "detail".
Update: I'm currently calling "scrollToPosition" on the UI-Thead after the data has been loaded, but I doubt this is the correct way of doing this or am I wrong?
You shouldn't be adding stuff to your dataset, you will sooner or later run out of memory. What you can do is return a big number (I used Short.MAX_VALUE) item in getItemCount inside your adapter and in the method that requests a view for postion you should do position % list.size();
It is not a truly endless RecyclerView this way, but good enough. I will paste some code tomorrow, I don't have it here now :/
I think you have to add items inside your adapter. Let`s say
class Adapter extends Recycler.Adapter<Recycler.ViewHolder>{
List<YourCustomObject> list;
public Adapter(){
list = new ArrayList<>();
}
public void addItem(YourCustomObject item){
list.add(item);
notifyItemDateSetChanged(); //This method for adapter to notice that list size have been changed
}
// Here your views
}
There is implementation of Your fragment or Activity where you retrieve data from internet.Let` say
class MainActivity extends AppCompactActivity{
Adapter adapter = new Adapter();
List<YourCustomObjects> objects;
public void onCreateView(){
//////// Something yours
}
public void onLoadMore(){
///// Your operation to retrieve data and init it to your list objects
for(YourCustomObject object : objects){
adapter.addItem(object);
}
}
}
This question already has answers here:
How to update RecyclerView Adapter Data
(16 answers)
Closed 5 years ago.
When I have to use a classic adapter with a ListView, I update my data in the ListView like this:
myAdapter.swapArray(data);
public swapArray(List<Data> data) {
clear();
addAll(data);
notifyDataSetChanged();
}
I would like to know what is the best practice for a RecyclerView. Because in a RecyclerView adapter you can't do a clear and addAll as in ListView.
So I tried just with a notifyDataSetChanged, but it didn't work.
Then I tried with a swapAdapter on my view:
List<Data> data = newData;
MyRecyclerAdapter adapter = new MyRecyclerAdapter(data);
// swapAdapter on my recyclerView (instead of a .setAdapter like with a classic listView).
recyclerViewList.swapAdapter(adapter, false);
But with this last solution, I still have to create a new instance of my adapter and I feel like it's not the best solution. I should be able just to change my data without a new MyRecyclerAdapter.
RecyclerView's Adapter doesn't come with many methods otherwise available in ListView's adapter. But your swap can be implemented quite simply as:
class MyRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
List<Data> data;
...
public void swap(ArrayList<Data> datas)
{
data.clear();
data.addAll(datas);
notifyDataSetChanged();
}
}
Also there is a difference between
list.clear();
list.add(data);
and
list = newList;
The first is reusing the same list object. The other is dereferencing and referencing the list. The old list object which can no longer be reached will be garbage collected but not without first piling up heap memory. This would be the same as initializing new adapter everytime you want to swap data.
#inmyth's answer is correct, just modify the code a bit, to handle empty list.
public class NewsAdapter extends RecyclerView.Adapter<...> {
...
private static List mFeedsList;
...
public void swap(List list){
if (mFeedsList != null) {
mFeedsList.clear();
mFeedsList.addAll(list);
}
else {
mFeedsList = list;
}
notifyDataSetChanged();
}
I am using Retrofit to fetch the list, on Retrofit's onResponse() use,
adapter.swap(feedList);
DiffUtil can the best choice for updating the data in the RecyclerView Adapter which you can find in the android framework. DiffUtil is a utility class that can calculate the difference between two lists and output a list of update operations that converts the first list into the second one.
Most of the time our list changes completely and we set new list to RecyclerView Adapter. And we call notifyDataSetChanged to update adapter. NotifyDataSetChanged is costly. DiffUtil class solves that problem now. It does its job perfectly!
Found following solution working for my similar problem:
private ExtendedHashMap mData = new ExtendedHashMap();
private String[] mKeys;
public void setNewData(ExtendedHashMap data) {
mData.putAll(data);
mKeys = data.keySet().toArray(new String[data.size()]);
notifyDataSetChanged();
}
Using the clear-command
mData.clear()
is not nessescary