I am using Android Data Binding with a RecyclerView.Adapter.
On Adapter's onCreateViewHolder I call:
public TransfersViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new MyViewHolder(
ListItemMyDataBinding.inflate(
LayoutInflater.from(parent.getContext()),
parent,
false));
}
So ListItemMyDataBinding is a representation of list_item_my_data.xml.
Adapter's onBindViewHolder looks something like this:
#Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
MyData myData = mDataList.get(position);
holder.render(myData);
....
}
And finally, the render function of my MyViewHolder looks like this:
public void render(MyData data, boolean expand) {
mBinding.setData(data); // mBinding is an instance of ListItemMyDataBinding
}
And in my xmls there are some data bindings:
<data>
<import type="com.example.MyData" />
<variable
name="data"
type="MyData" />
</data>
...
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{data.userName}" />
I use this approach in a searchable list, so I have a search field on top of the list. Once a user enters a new search term, list is cleared and notified about change, once the response comes back from server I add all results to the list and notify about the change again.
And finally the problem is that the following happens:
new search term entered: list cleared, items removed [OK]
response arrives back from server [OK]
I add all items to the list and notify adapter about the change [OK]
for a fraction of a second the old values are appearing in the list and list items will be updated with the new values only after that [NOT OK]
As I understand, this is because the Views are reused and as a first step the adapter realises that it can reuse a view and displays it, and only (a fraction of seconds) after it applies the changes in the layout (through databinding), so that's why I see the old values blinking up. Which is quite annoying.
If in function render I manually set a default value to the views (for example setText("") on TextViews) then I cannot see that blinking effect, but this is not a solution that I want.
Do you have any suggestions how can I nicely avoid this "blinking/lag" effect, what am I doing wrong?
Thanks
call
mBinding.executePendingBindings ();
just after mBinding.setData(data);
Related
I am using a RecyclerView for a list. Each layout item of the RecyclerView has a Button. This button is supposed to save some data(that which I bound to the view).
I thought that since the same button is in every view item, why not have a single static listener instance that I can bind to all the buttons. Because that would save some memory.
Now, a few points about the listener(View.OnClickListener()):
It is stored as a static member of my RecyclerView.Adapter class and gets instantiated once when the data is being bound for the first item.
The instance is created as an anonymous instance and inside a method( onBindViewHolder(ViewHolder, int position))
Inside the onClick(View v) method of the listener, I access the data to be bound. This data is retrieved from the list of data objects stored as Adapter source using position provided in this method( onBindViewHolder(ViewHolder, int position)).
Now, the problem:
When I click on button and check what item is going to be saved by the listener, I find that the first data entry in the RecyclerView is the one that gets saved corresponding to the clicks of any of the items of the RecyclerView.
I have no idea why this is happening. Can anybody find the cause of it?
Secondly, is this a good strategy to have a single listener for multiple list items?
Note: I am unable to post the code for it. Sorry about that.
The best way to handle RecyclerView clicks is like this:
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = layoutInflater.inflate(R.layout.list_item, parent, false);
final ViewHolder viewHolder = new ViewHolder(view);
viewHolder.actionButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
int itemPosition = viewHolder.getAdapterPosition();
handleActionButtonClick(itemPosition);
}
});
return viewHolder;
}
It is simple and robust. Having single click listener for all the items is an extreme example of over optimisation. These few extra bytes will never matter and it will come with cost of extra boilerplate.
If you are getting the response based upon the first item of the Recyclerview, then to get the actual postion use getAdapterPosition() at ViewHolderclass or onBindView() and getTag() might as well help to get the exact data
Since RecyclerView(as its name suggest) recycles its child views, so there is no memory related issues.
I am using the RealmRecyclerView from this post:https://realm.io/news/android-realm-listview/
In this ToDo app, when item is swiped, it is automatically deleted from RecyclerView as well as Realm Database.
I want to get notified when an item is deleted, also which item is deleted so I can perform some action with that item.
I tried using the Realm Change Listener, but that is invoked every time when a Realm Transaction is committed. So it is invoked even when a new item is added.
How do I do this? Is it possible with normal RecyclerView?
At this moment (with version V1.1.0), Realm does not provide a callback when a RealmObject is deleted. This is true for all types of data/views (listView, RecyclerView, RealmObject, RealmResults etc), also true if you query data asObservable or use a change listener.
However it does make sense to send an empty object, when the queried object is deleted from Realm, but since its a breaking change in Realm, we will have to wait for V2.0.
More details - https://github.com/realm/realm-java/issues/3138
According to the answer, they have not yet added this feature. But if you want to achieve this, you can use normal ReacyclerView using the existing RealmAdapter and everything as it is.
Here's how to do it:-
Remove the RealmRecyclerView and add the normal RecyclerView:-
1. Add the normal RecyclerView from the support library.
2. Initialize the recyclerview with the existing adapter i.e the adapter class that extends RealmBasedRecyclerViewAdapter, no need to make a new adapter
recyclerView=(RecyclerView)findViewById(R.id.realm_recyeler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new FilterAdapter(this,results,true,true);
recyclerView.setAdapter(adapter);
3. Next, we will use the ItemTouchHelper class to implement the swipe to dismiss for the RecyclerView :-
ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(0,ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(RecyclerView recyclerView,RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
adapter.remove(viewHolder.getAdapterPosition(),alarmintent);
}
};
I've made a method in my adapter to remove item(shown below), you can do it here as well
The viewHolder.getAdapterPositon() gives the position of item swiped, it is passed to delete the RealmObject from Realm DB at given position(shown below)
0 -> drag flag - since I am not implement drag to move items, I've kept it as zero
ItemTouchHelper.RIGHT - swipe flags - These say in which direction the swipe to dismiss is set
ItemTouchHelper.RIGHT - swipe in right direction to dismiss
ItemTouchHelper.LEFT - swipe in left direction to dismiss
To support both directions, pass- ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
itemTouchHelper.attachToRecyclerView(recyclerView);
Create a new ItemTouchHelper object with above callback
Attach the ItemTouchHelper to RecyclerView
4. Here's how to remove the item (below is the code of my remove method of adapter):-
public void remove(int position)
{
RealmConfiguration configuration = new RealmConfiguration.Builder(context).deleteRealmIfMigrationNeeded().build();
realm = Realm.getInstance(configuration);
realm.beginTransaction();
realmResults.deleteFromRealm(position);
realm.commitTransaction();
notifyItemRemoved(position);
}
deleteFromRealm method is used to delete item at given position
call the notifyItemRemoved(position) to indicate item is removed at specified position
5. That's it, very easy and no need to create new adapters etc.
While Realm may not provide a callback for when an item is deleted, there is a way to know when items are deleted when using the RealmRecyclerView from this post.
The adapter (RealmBasedRecyclerViewAdapter) will call onItemSwipedDismiss(int position) whenever an item is swiped for dismissal. In your subclass of this adapter, you can override this method to add some extra logic.
For example, in my Recycler View, I want to give users the option to Undo a deletion. So, I override onItemSwipedDismiss(int position) and access the fields of the object being deleted. (In my case, this object is fairly small -- only three fields -- so this isn't too unwieldy). Then I call the super method: super.onItemSwipedDismiss(position); which will animate the deletion and remove it from Realm.
Then, I create a Snackbar with an action that re-creates the Realm object from the saved fields. Once it's created, it immediately goes back into the recycler view.
Here's a skeleton of the implementation of this method override:
#Override
public void onItemSwipedDismiss(int position) {
// Gather the object's fields, if you want:
YourObject objectToDelete = realmResults.get(position);
final String title = objectToDelete.getTitle();
final long timestamp = objectToDelete.timestamp;
// Perform delete and animation:
super.onItemSwipedDismiss(position);
// Add code here depending on what you want to do
// (for example, you could add a Snackbar that undoes
// the deletion by "resurrecting" your deleted object)
}
I have a recyclerView in which i have a list of items displayed in a LinearLayout.There is a "increase" button in every list which will increment a quantity by "1".But when I click the button on the first list item to increment the number..the incremented value is displayed in the last list item of the recyclerView not in the desired position where i clicked.Can anyone help me find the solution?
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
myholder = holder;
holder.item_name_text.setText(data.get(position).getName());
holder.item_price_text.setText(data.get(position).getPrice().toString());
holder.item_quantity_text.setText("500");
//Adding item to the cart
holder.add_image.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(totalItem>=0 && totalItem<10){
totalItem++;
myholder.item_totalquantity_text.setText(String.valueOf(totalItem));
}else{
Toast.makeText(myholder.itemView.getContext(),"Cannot add more item",Toast.LENGTH_SHORT).show();
}
}
});
So I always think of recyclerviews as the frontend that displays my data. If you would like to make changes to the actual data itself and make sure it gets reflected in the recyclerview, you will need to ensure the changes are made inside the arraylist of data objects.
You will need to add a field inside the data object and call it counter. Then you must increment the counter once the user clicks on the onClickListener.
myholder.item_totalquantity_text.setText(data.get(position).setCounter(data.get(position).getCounter())+1);
Do not set OnClickListener() in onBindViewHolder(). Instead do it in your ViewHolder class itself. And main ly as RecyclerView re-uses the holder objects to represent the new set of data that is becoming visible. So the listener on which yo click might be belonging to some other view holder so it appears there instead.
Have a quick read of this and this. These are not very relevant to you but it has info to understnad recycling concept so you can fix your stuff easily.
I am building a notifications list for an application I'm working on and I'm having trouble finding a way to take my list of notifications from the server and displaying them in separate lists in a RecyclerView. The end product would display the list of notifications with headers for Recent notifications and Older notifications, a la:
<RECENT HEADER>
<NOTIF-1>
<NOTIF-2>
<OLDER HEADER>
<NOTIF-3>
<NOTIF-4>
<NOTIF-5>
<NOTIF-6>
except instead of angle-bracket text it's actual views representing those, complete with images, actual notification details and dividers.
I already have code that displays them in a RecyclerView:
XML:
<!-- Main layout -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="#layout/include_toolbar"/>
<RelativeLayout
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/notification_swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.mapjungle.mymoose.ui.widget.EmptyRecyclerView
android:id="#+id/notification_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v4.widget.SwipeRefreshLayout>
</RelativeLayout>
</LinearLayout>
Java:
#InjectView(R.id.notification_list) RecyclerView mRecyclerView;
#Inject Picasso mPicasso;
#Inject NotificationService mUserService;
private NotificationAdapter mAdatper;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notifications);
ButterKnife.inject(this);
setTitle("Notifications");
mAdatper = new NotificationAdapter(mPicasso);
mRecyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(this)
.color(getResources().getColor(R.color.secondary_color))
.size(1)
.build());
final LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setAdapter(mAdatper);
updateList();
}
#Override
protected int getSelfNavDrawerItem() {
return NAVDRAWER_ITEM_PHOTO_POST;
}
public void updateList() {
mUserService.getNotifications(new Callback<List<Notification>>() {
#Override
public void success(List<Notification> notificationList, Response response) {
mAdatper.replaceWith(notificationList);
}
#Override
public void failure(RetrofitError error) {
Timber.e(error, "Failed to load notifications...");
}
});
}
This all works fine enough to display all of the notifications and they're all sorted in the order from newest to oldest descending. But each has a boolean property "acknowledged" that is set to false if the user hasn't seen them before. I want to put split the list into the two groups I've explained above using this flag, but I don't know how to throw in the headers. I've thought about subclassing Notification to create NotificationHeader views and inserting them into the list where appropriate but that just feels sloppy to me. I've also thought about doing two recycler views, one for the new and another for the old, but visually that didn't work the way I intended (I haven't confirmed it but it looked like each recycler view scrolled independently of the others, something that I do not want). Any suggestions?
I know that the first idea of creating special Notification Headers will probably work, I've done something like that before, but it just feels like bad practice.
RecyclerView.Adapter has a method called getItemViewType() that takes the position of an item in the adapter's list, and returns the view type it should use. In my case, the method looks like this:
#Override
public int getItemViewType(int position){
Notification n = mNotifications.get(position);
boolean useHeader = n.getType().equals(Notification.HEADER_OLDER) ||
n.getType().equals(Notification.HEADER_RECENT);
return useHeader ? this.USE_HEADER : this.DONT_USE_HEADER;
}
Which checks the items in the notification list and sees if they're a special static 'Header notification' object. This is used internally by the Adapter class and it passes the 'viewType' parameter to the onCreateViewHolder() method, which we also override:
#Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
int layout = viewType == USE_HEADER ?
R.layout.view_item_notification_header :
R.layout.view_item_notification;
NotificationItemView view = (NotificationItemView) LayoutInflater.from(viewGroup.getContext())
.inflate(layout, viewGroup, false);
return new ViewHolder(view);
}
Overriding this method allows us to use the viewType parameter to choose the appropriate layout to inflate for the ViewHolder.
There are some better style/good practice decisions things I should have done here, such as making my Notification adapter hold a list of NotificationListItems instead of Notifications, which would allow me to put in a new kind of NotificationHeader object on it's own instead of making Notification objects that weren't really Notifications and using a bunch of constant values. But the underlying principle is still there:
In your Model, have a method that returns the layout view to use for it
In your adapter override getItemViewType() to use the aforementioned method and return an int that corresponds to the layout that should be inflated
In your adapter also override onCreateViewHolder() to use the int from getItemViewType() and inflate the appropriate view accordingly
I am new to android development so may be i am missing something elementary here, but after almost 2 days of googling I am still unable to figure out the cause.
I have followed the following article to create my project, except that I am invoking this activity from another activity when a button is clicked.
I have also created my own List adopter public class MyListAdapter extends ArrayAdapter<MyData> and in the getView() method of this adapter I am actually providing a different representation like below.
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.my_list_view, parent, false);
}
this all works fine I am able to bind the data and its displaying the results on the list without any issues. Now I want to show the details of selected MyData when the user clicks on the list item, but this is just not working no matter what I try.
I have ensured that my activity is implementing the callback interface like below:
public class MyItemListActivity extends FragmentActivity
implements MyItemListFragment.Callbacks
and the callback itself is also very simple:
public interface Callbacks {
public void onItemSelected(MyData item);
}
also on my MyItemListFragment the onAttched is correctly hooked up mCallbacks = (Callbacks) activity;
What I really notice is that the following function is just not called when I click on the list item:
#Override
public void onListItemClick(ListView listView, View view, int position, long id) {
super.onListItemClick(listView, view, position, id);
MyData data = DataContent.ITEMS.get(position);
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
mCallbacks.onItemSelected(data);
}
my fragment that displays the list item looks like this:
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/myitem_list"
android:name="path.MyItemListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
tools:context="path.MyItemListActivity"
tools:layout="#android:layout/list_content" />
and the actual custom adapter view my_list_view looks like below:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >....</RelativeLayout>
I can see that its missing the android:onClick tag here, but not sure if that is the cause and also who needs to implement this handler as there is no direct context on this layout.
Any help is really appreciated.
Regards
Kiran