public class MyAdapterMarket extends RecyclerView.Adapter<MyAdapterMarket.MyViewHolder> {
Context c;
ArrayList<MarketObject> marketObject;
public RecyclerView recyclerView;
public MyAdapterMarket(MarketActivity.MyFragment c, ArrayList<MarketObject> marketObject) {
this.marketObject = marketObject;
}
// I AM ABLE TO GET UPDATED OBJECT HERE BUT HOW CAN I UPDATE IN RECYCLEVIEW AT PARTICULAR POSITION. // marketObject.getLast() gives updated value in Realm Database
public static void updateEmployeeListItems(MarketObject marketObject) {
Log.e("UPDATED OBJECT FROM REALM DATABASE",""+marketObject.getLast());
}
#Override
public MyAdapterMarket.MyViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.calender_market_list, parent, false);
return new MyAdapterMarket.MyViewHolder(v);
}
public class MyViewHolder extends RecyclerView.ViewHolder {
public TextView market_title, country_name, market_last, market_time, market_dailyPercentageChange, market_dailyChange;
public LinearLayout actual, previous, constant;
public MyViewHolder(View itemView) {
super(itemView);
c = itemView.getContext();
recyclerView = (RecyclerView) itemView.findViewById(R.id.calendar_fragment_recycleview);
market_title = (TextView) itemView.findViewById(R.id.market_title);
country_name = (TextView) itemView.findViewById(R.id.country_name);
market_last = (TextView) itemView.findViewById(R.id.market_last);
market_time = (TextView) itemView.findViewById(R.id.market_time);
market_dailyPercentageChange = (TextView) itemView.findViewById(R.id.market_dailyPercentageChange);
market_dailyChange = (TextView) itemView.findViewById(R.id.market_dailyChange);
}
}
#Override
public void onBindViewHolder(MyAdapterMarket.MyViewHolder holder, final int position) {
final MarketObject market = marketObject.get(position);
holder.market_title.setText(market.getName());
holder.country_name.setText(capitalize(market.getCountry()));
holder.market_last.setText(market.getLast());
holder.market_time.setText(market.getTime());
holder.market_dailyPercentageChange.setText(market.getDailypercentualchange());
holder.market_dailyChange.setText(market.getDailychange());
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Log.v("RECYCLEVIEW POSITION", "Element " + getAdapterPosition() + " clicked.");
AutoUpdate(position, "0.007");
Context context = v.getContext();
Intent intent = new Intent(context, MarketDetail.class);
intent.putExtra( "NAME" , market.getName());
//context.startActivity(intent);
}
});
}
#Override
public int getItemCount() {
return marketObject.size();
}
}
Give code is my Adapter code, Now I am getting live data of market which update perfectly in Realm Database with MarketObject. I want to reflect that data in Recycleview also with animation so a user can see this market Rate is updated. How does it possible. While updating this value in Realm Database I am passing Object in this function "updateEmployeeListItems()". Now I am not able to update in RecycleView at a particular position.
In the following Image when I get updates from server I am updating data in Realm Database and as I explain in "updateEmployeeListItems()" I am getting updated object from, Now I want to update this object data in RecycleView
Okay so if you want to leverage Realm's fine-grained notifications, then you should be using RealmRecyclerViewAdapter, and on top of that
public class MyAdapterMarket extends RealmRecyclerViewAdapter<MarketObject, MyAdapterMarket.MyViewHolder> {
public MyAdapterMarket(OrderedRealmCollection<MarketObject> results {
super(results, true, false); // passing `false` to handle `change` manually
}
#Override
public MyAdapterMarket.MyViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
return new MyAdapterMarket.MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.calender_market_list, parent, false));
}
public class MyViewHolder extends RecyclerView.ViewHolder {
Context c;
RecyclerView recyclerView;
TextView market_title, country_name, market_last, market_time, market_dailyPercentageChange, market_dailyChange;
LinearLayout actual, previous, constant;
public MyViewHolder(View itemView) {
super(itemView);
c = itemView.getContext();
recyclerView = (RecyclerView) itemView.findViewById(R.id.calendar_fragment_recycleview);
market_title = (TextView) itemView.findViewById(R.id.market_title);
country_name = (TextView) itemView.findViewById(R.id.country_name);
market_last = (TextView) itemView.findViewById(R.id.market_last);
market_time = (TextView) itemView.findViewById(R.id.market_time);
market_dailyPercentageChange = (TextView) itemView.findViewById(R.id.market_dailyPercentageChange);
market_dailyChange = (TextView) itemView.findViewById(R.id.market_dailyChange);
}
RealmObjectChangeListener realmChangeListener = new RealmObjectChangeListener<MarketObject>() {
#Override
public void onChange(MarketObject market, ObjectChangeSet changeSet) {
if(changeSet.isDeleted()) return;
if(changeSet.isFieldChanged("name")) {
market_title.setText(market.getName());
}
if(changeSet.isFieldChanged("country")) {
country_name.setText(capitalize(market.getCountry()));
}
if(changeSet.isFieldChanged("last")) {
market_last.setText(market.getLast());
}
if(changeSet.isFieldChanged("time")) {
market_time.setText(market.getTime());
}
if(changeSet.isFieldChanged("dailypercentualchange")) {
market_dailyPercentageChange.setText(market.getDailypercentualchange());
}
if(changeSet.isFieldChanged("dailychange")) {
market_dailyChange.setText(market.getDailychange());
}
}
}
MarketObject market;
public void bind(MarketObject market) {
if(this.market != null && this.market.isValid()) {
this.market.removeAllChangeListeners();
}
this.market = market;
market.addChangeListener(realmChangeListener);
market_title.setText(market.getName());
country_name.setText(capitalize(market.getCountry()));
market_last.setText(market.getLast());
market_time.setText(market.getTime());
market_dailyPercentageChange.setText(market.getDailypercentualchange());
market_dailyChange.setText(market.getDailychange());
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Log.v("RECYCLEVIEW POSITION", "Element " + getAdapterPosition() + " clicked.");
AutoUpdate(position, "0.007");
Context context = v.getContext();
Intent intent = new Intent(context, MarketDetail.class);
intent.putExtra( "NAME" , market.getName());
//context.startActivity(intent);
}
});
}
}
#Override
public void onBindViewHolder(MyAdapterMarket.MyViewHolder holder, final int position) {
final MarketObject market = marketObject.get(position);
holder.bind(market);
}
}
If you're only updating one item, you can update your backing list with the new item (i.e. replacing it at old item's index), then tell the RecyclerView adapter about the change by calling recyclerView.getAdapter().notifyItemChanged(POSITION), where POSITION is the integer index of your updated item in the list/array.
If you get an entire new list of items, you can use the DiffUtil to find the diff between the old list of backing items and the updated list of backing items, then dispatch those changes to the RecyclerView adatper:
That being said, user #Saran Sankaran's suggestion of using RealmRecyclerViewAdapter is probably what you're looking for. If you use that, you don't need to do any of the notifyItemChanged logic yourself.
Related
I am new to Android Development i am trying to sort item position in Recyclerview i am useing Realm Database and my data-set for Adapter is RealmResults<ShoppingList>
here is my Adapter please take look my moveItemDown and updateItemPosition method i m trying to update item position in updateItemPosition Method but it's not working. i also attached my view below the adapter
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.MyViewHolder> {
private RealmResults<ShoppingList> dataList;
private Realm mRealm;
private LayoutInflater mInflater;
public RecyclerAdapter(Context context, RealmResults<ShoppingList> data, Realm reamDatabaseInstence){
this.dataList = data;
this.mInflater = LayoutInflater.from(context);
mRealm = reamDatabaseInstence;
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Log.i("Recycler", "onCreateViewHolder");
View view = mInflater.inflate(R.layout.shopping_list_item, parent,false);
MyViewHolder holder = new MyViewHolder(view);
return holder;
}
#Override
public int getItemCount() {
return dataList.size();
}
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
Log.i("Recycler", "onBindViewHolder " + position);
ShoppingList currentItem = dataList.get(position);
holder.setData(currentItem, position);
holder.setListener();
}
public void moveItemDown(int position){
if( position == (dataList.size()-1) ) { return; }
int oi = position;
int ni = position+1;
updateItemPosition(oi, ni);
notifyDataSetChanged();
}
public void moveItemUp(int position){
// Need to implement after down sort work
}
private void updateItemPosition(final int pos, final int new_pos){
mRealm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
ShoppingList old_item = realm.where(ShoppingList.class)
.equalTo("position", pos)
.findFirst();
old_item.setPosition(new_pos);
Log.d("UpdateData: ",old_item.getTitle()+" "+old_item.getPosition());
ShoppingList new_item = realm.where(ShoppingList.class)
.equalTo("position", new_pos)
.findFirst();
new_item.setPosition(pos);
Log.d("UpdateData: ",new_item.getTitle()+" "+new_item.getPosition());
}
});
}
class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
TextView title;
ImageButton up, down;
ImageView image;
int position;
ShoppingList current;
MyViewHolder(View itemView) {
super(itemView);
title = (TextView) itemView.findViewById(R.id.shopping_card_view_title);
image = (ImageView) itemView.findViewById(R.id.shopping_card_view_image);
up = (ImageButton) itemView.findViewById(R.id.shopping_card_view_up_btn);
down = (ImageButton) itemView.findViewById(R.id.shopping_card_view_down_btn);
}
void setData(ShoppingList i, int position) {
title.setText( i.getTitle() );
image.setImageResource(i.getImageId());
this.position = position;
this.current = i;
}
#Override
public void onClick(View view) {
switch (view.getId()){
case R.id.shopping_card_view_up_btn:
moveItemUp(position);
break;
case R.id.shopping_card_view_down_btn:
moveItemDown(position);
break;
}
}
void setListener() {
up.setOnClickListener(MyViewHolder.this);
down.setOnClickListener(MyViewHolder.this);
}
}
}
It's late reply by it would help to you and this is the combination of both answer EpicPandaForce give a great example to work with RealmRecyclerViewAdapter and make your Recyclerview automated.
Second Option
this is to use UNIQUE ID FIELD in order to make changes in list item.
Here is the problem happening in you Code
Basically when you get an item by it's position for instance position=1; and you updated to position 2 right? notice now you have two item with same position = 2 and now when you try to get first item with position = 2 Realm return the same item that you updated from position 1 to 2 now what happening here you updated dated position back to position=1;
taman neupane give you good idea but he is not describe much.
Please TRY THIS
private void updateItemPosition(final int pos, final int new_pos){
final String current_item_id = dataList.get(pos).get_id();
final String new_item_id = dataList.get(new_pos).get_id();
mRealm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
// Try to find the result with unique id replace this .equalTo("Your Unique ID Field name", current_item_id)
ShoppingList old_item = realm.where(ShoppingList.class).equalTo("position", pos).findFirst();
old_item.setPosition(new_pos);
// Also here Try to find the result with unique id replace this .equalTo("Your Unique ID Field name", new_item_id)
ShoppingList new_item = realm.where(ShoppingList.class).equalTo("position", new_pos).findFirst();
new_item.setPosition(pos);
//new you can tell you adapter about the changes in dataset.
notifyItemMoved(pos, new_pos);
notifyItemRangeChanged(pos, dataList.getSize());
}
});
}
and then update your both function with that
public void moveItemDown(int position){
if( position == (dataList.size()-1) ) { return; }
int oi = position;
int ni = position+1;
updateItemPosition(oi, ni);
}
public void moveItemUp(int position){
if( position == 0 ) { return; }
int oi = position;
int ni = position-1;
updateItemPosition(ni, oi);
}
And That's it.
If anyone want to modify my explanation i will appreciate because i am not good in english.
Happy Coding.
To get automatic updates, you should use RealmRecyclerViewAdapter from https://github.com/realm/realm-android-adapters
public class RecyclerAdapter extends RealmRecyclerViewAdapter<ShoppingList, RecyclerAdapter.MyViewHolder> { // <-- !
private Realm mRealm;
private LayoutInflater mInflater;
public RecyclerAdapter(Context context, RealmResults<ShoppingList> data, Realm realm){
super(data, true);
this.mInflater = LayoutInflater.from(context);
mRealm = realm;
}
and replace dataList with getData()
I think your ShoppingList item has unique id for all. Just make id field primary and use realmInsertorUpdate feature . You don't have to check for any thing else. If the id is available it will update the database and if id is not available it will just insert.
I have created a simple RecycleView component. Then create a new activity for that component. In one row I have a TextView and Button. What I want to do is to change something in object which was put into the excatly one row.
First I initialized the Button:
public class RecycleViewAllWashesActivity extends Activity {
private Button isFavorite;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.row_recycleview);
isFavorite = (Button) findViewById(R.id.addToFav);
isFavorite.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.w("myApp", String.valueOf(v.getId()));
int id = v.getId();
}
});
}
}
The adapter of RecycleView looks like:
#Override
public MyAdapter.WashLocationViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
// Inflate the custom layout
View view = inflater.inflate(R.layout.row_recycleview, parent, false);
// Return a new holder instance
WashLocationViewHolder viewHolder = new WashLocationViewHolder(view);
return viewHolder;
}
// Involves populating data into the item through holder
#Override
public void onBindViewHolder(MyAdapter.WashLocationViewHolder viewHolder, int position) {
// Get the data model based on position
WashLocation w = washLocations.get(position);
String info = w.getWashName();
// Set item views based on your views and data model
TextView textView = viewHolder.info;
textView.setText(info);
Integer fav = w.getFav();
Boolean favorite = fav == 1 ? true : false;
Button addToFav = viewHolder.favorite;
addToFav.setText(favorite == true ? "Usuń z ulubionych" : "Dodaj do ulubionych");
}
Questions:
How to get the object which was put into one row?
When I clicked the Button (code above) it doesn't react, even breakpoint which was set there doesn't react? -> the thing in console which I see while clicking the button is:
04-17 13:11:00.137 7671-7671/com.example.micha.locationtest D/ViewRootImpl: ViewPostImeInputStage ACTION_DOWN
==============================
Update:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ItemHolder> {
private List<WashLocation> washLocations;
private OnItemClickListener onItemClickListener;
private LayoutInflater layoutInflater;
public MyAdapter(List<WashLocation> washLocations, Context context) {
layoutInflater = LayoutInflater.from(context);
this.washLocations = washLocations;
}
#Override
public MyAdapter.ItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = layoutInflater.inflate(R.layout.row_recycleview, parent, false);
return new ItemHolder(itemView, this);
}
#Override
public void onBindViewHolder(MyAdapter.ItemHolder holder, int position) {
// Get the data model based on position
WashLocation w = washLocations.get(position);
String info = w.getWashName();
// Set item views based on your views and data model
TextView textView = holder.info;
textView.setText(info);
Integer fav = w.getFav();
Boolean favorite = fav == 1 ? true : false;
Button addToFav = holder.favorite;
addToFav.setText(favorite == true ? "Usuń z ulubionych" : "Dodaj do ulubionych");
}
#Override
public int getItemCount() {
return washLocations.size();
}
public void setOnItemClickListener(OnItemClickListener listener) {
onItemClickListener = listener;
}
public OnItemClickListener getOnItemClickListener() {
return onItemClickListener;
}
public interface OnItemClickListener {
public void onItemClick(ItemHolder item, int position);
}
/* public void add(int location, String iName){
itemsName.add(location, iName);
notifyItemInserted(location);
}
public void remove(int location){
if(location >= itemsName.size())
return;
itemsName.remove(location);
notifyItemRemoved(location);
}*/
public static class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private MyAdapter parent;
TextView textItemName;
public TextView info;
public Button favorite;
public ItemHolder(View itemView, MyAdapter parent) {
super(itemView);
itemView.setOnClickListener(this);
this.parent = parent;
info = (TextView) itemView.findViewById(R.id.textView);
favorite = (Button) itemView.findViewById(R.id.addToFav);
}
public void setItemName(CharSequence name) {
textItemName.setText(name);
}
public CharSequence getItemName() {
return textItemName.getText();
}
#Override
public void onClick(View v) {
final OnItemClickListener listener = parent.getOnItemClickListener();
if (listener != null) {
listener.onItemClick(this, getPosition());
}
}
}
}
The activity class:
final DataBaseHelper dataBaseHelper = new DataBaseHelper(getApplicationContext());
washLocations = dataBaseHelper.getWashLocation();
mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
mAdapter = new MyAdapter(washLocations, this);
mAdapter.setOnItemClickListener(this);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
#Override
public void onItemClick(MyAdapter.ItemHolder item, int position) {
Log.d("myApp", "fdsgfds");
}
In Recycle review you must set an onClickListener for the views explicitly.
It does not have any default methods to handle click events like the listview.
You need to set an Onclick listener for your button in your WashLocationViewHolder
You will receive a click event there. From the view holder, you can send the event back to your fragment or activity using an interface.
if you need further help regarding this, mention it in the comments. I will provide you some samples.
How to get the object which was put into one row?
To do this you can set the object as a Tag using the setTag(Object) method in the layout or the button for which you will be providing a click listener. Then you can get the object using the getTag() method from the view.
Hope this helps.
I have a RecyclerView defined in my android project. The Adapter for the RecyclerView is as follows
public class CustomAdapter1 extends RecyclerView.Adapter<CustomAdapter1.MyViewHolder> {
private Fragment1 myFrag;
private ArrayList<DataModel1> dataSet;
DBHandler db;
public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView butAccept,butReject;
TextView nick,stat,tit,cat,desc,dt;
public CardView cardView;
public MyViewHolder(View itemView) {
super(itemView);
this.nick = (TextView) itemView.findViewById(R.id.nick);
this.stat = (TextView) itemView.findViewById(R.id.stat);
this.tit = (TextView) itemView.findViewById(R.id.tit);
this.cat = (TextView) itemView.findViewById(R.id.cat);
this.desc = (TextView) itemView.findViewById(R.id.desc);
this.dt = (TextView) itemView.findViewById(R.id.dt);
this.butAccept = (TextView)itemView.findViewById(R.id.accept_textview);
cardView = (CardView) itemView.findViewById(R.id.card_view);
cardView.setOnClickListener(this);
butAccept.setOnClickListener(this);
db = new DBHandler(itemView.getContext());
}
#Override
public void onClick(View v) {
if( v.getId() == butAccept.getId()){
String x = String.valueOf(dataSet.get(getAdapterPosition()).getMeetId());
myFrag.showToast(x);
}
}
}
public CustomAdapter1(ArrayList<DataModel1> data,Fragment1 frag) {
this.dataSet = data;
myFrag = frag;
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent,int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.cardfrag1, parent, false);
MyViewHolder myViewHolder = new MyViewHolder(view);
return myViewHolder;
}
#Override
public void onBindViewHolder(final MyViewHolder holder,final int listPosition) {
TextView nick = holder.nick;
TextView stat = holder.stat;
TextView tit = holder.tit;
TextView cat = holder.cat;
TextView desc = holder.desc;
TextView dt= holder.dt;
nick.setText(dataSet.get(listPosition).getNick());
stat.setText(dataSet.get(listPosition).getStat());
tit.setText(dataSet.get(listPosition).getTit());
cat.setText(dataSet.get(listPosition).getCat());
desc.setText(dataSet.get(listPosition).getDesc());
dt.setText(dataSet.get(listPosition).getDt());
}
#Override
public int getItemCount() {
return dataSet.size();
}
}
The final TextView butAccept has dynamic values.I have an internal DB that has the column Status. When the value of the Status is 1 I need to change the text that is being displayed in the TextView butAccept.
How should I do this? Should I write a function in my CustomAdapter, or in the fragment in which I am using the adapter?
In my Fragment I have a function :
private void checkTheAcceptStatus(int x){
int status = db.getMeetingStatus(x);
Toast.makeText(getActivity(),String.valueOf(status),Toast.LENGTH_SHORT).show();
if(status == 1){
}
}
This function is being called everytime a new item is added to the adapter. The internal DB is being queried correctly and if the value of Status is being returned. If the value is 1 what should I do?
I saw this question, but this is unclear and I was unable to come to any conclusion from this.
If I understood correctly every row in your list has this accept button. So in your onBindViewHolder() you cant set the buttons text depending on the state of your status variable. When you detect changes in your db you can use the various notify methods provided depending on your needs: notifyItemChanged(...), notifyItemRangeChanged(...), notifyItemAdded(...), notifyItemRemoved(...) etc. Your onBindViewHolder should look something like this:
#Override
public void onBindViewHolder(final ViewHolder vh, int position) {
if(dataSet.get(position).getStatus() == 1){
vh.butAccept.setText("Your status 1 case text here");
} else {
vh.butAccept.setText("Your status not 1 case text here");
}
}
Also you can just use notifyDataSetChanged() if you want to refresh the whole list, but the methods above are more efficient.
I have a list of items that I created using RecyclerView. When the user clicks on one of them I change the background color of that selected item.
The problem is, when I scroll through my items, and they get recycled, some of the items get the selected item's background color (which is wrong).
Here you can see my Adapter's code:
public class OrderAdapter extends RecyclerView.Adapter<OrderAdapter.ViewHolder> {
private static final String SELECTED_COLOR = "#ffedcc";
private List<OrderModel> mOrders;
public OrderAdapter() {
this.mOrders = new ArrayList<>();
}
public void setOrders(List<OrderModel> orders) {
mOrders = orders;
}
public void addOrders(List<OrderModel> orders) {
mOrders.addAll(0, orders);
}
public void addOrder(OrderModel order) {
mOrders.add(0, order);
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
// Inflate the custom layout
View contactView = inflater.inflate(R.layout.order_main_item, parent, false);
// Return a new holder instance
ViewHolder viewHolder = new ViewHolder(contactView);
return viewHolder;
}
#Override
public void onBindViewHolder(final ViewHolder viewHolder, final int position) {
final OrderModel orderModel = mOrders.get(position);
// Set item views based on the data model
TextView customerName = viewHolder.customerNameText;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM/dd/yyyy' 'HH:mm:ss:S");
String time = simpleDateFormat.format(orderModel.getOrderTime());
customerName.setText(time);
TextView orderNumber = viewHolder.orderNumberText;
orderNumber.setText("Order No: " + orderModel.getOrderNumber());
Button button = viewHolder.acceptButton;
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
viewHolder.userActions.acceptButtonClicked(position);
}
});
final LinearLayout orderItem = viewHolder.orderItem;
orderItem.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
viewHolder.userActions.itemClicked(orderModel);
viewHolder.orderItem.setBackgroundColor(Color.parseColor(SELECTED_COLOR));
}
});
}
#Override
public int getItemCount() {
return mOrders.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder implements OrderContract.View {
public TextView customerNameText;
public Button acceptButton;
public TextView orderNumberText;
public OrderContract.UserActions userActions;
public LinearLayout orderItem;
public ViewHolder(View itemView) {
super(itemView);
userActions = new OrderPresenter(this);
customerNameText = (TextView) itemView.findViewById(R.id.customer_name);
acceptButton = (Button) itemView.findViewById(R.id.accept_button);
orderNumberText = (TextView) itemView.findViewById(R.id.order_number);
orderItem = (LinearLayout) itemView.findViewById(R.id.order_item_selection);
}
#Override
public void removeItem() {
}
}
The problem is recyclerView recycling behavior which assign your out of screen ViewHolder items to new items coming to be displayed on screen.
I would not suggest you to bind your logic based on ViewHolder object as in all above answers. It will really cause you problem.
You should build logic based on the state of your data object not ViewHolder Object as you will never know when it gets recycled.
Suppose you save a
state boolean isSelected in ViewHolder to check, but and if it is true, then the same state will be there for new Item when this viewHolder will be recycled.
Better way to do above is holding the any state in DataModel object. In your case just a boolean isSelected.
Sample example like
package chhimwal.mahendra.multipleviewrecyclerproject;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.support.v7.widget.CardView;
import android.widget.TextView;
import java.util.List;
/**
* Created by mahendra.chhimwal on 12/10/2015.
*/
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder> {
private Context mContext;
private List<DataModel> mRViewDataList;
public MyRecyclerViewAdapter(Context context, List<DataModel> rViewDataList) {
this.mContext = context;
this.mRViewDataList = rViewDataList;
}
#Override
public MyRecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.item_recycler_view, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.bindDataWithViewHolder(mRViewDataList.get(position));
}
#Override
public int getItemCount() {
return mRViewDataList != null ? mRViewDataList.size() : 0;
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView textView;
private LinearLayout llView;
private DataModel mDataItem=null;
public ViewHolder(View itemView) {
super(itemView);
llView=(LinearLayout)itemView.findViewById(R.id.ll_root_view);
textView = (TextView) itemView.findViewById(R.id.tvItemName);
cvItemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// One should handle onclick of event here based on the dataItem i.e. mDataItem in this case.
// something like that..
/* Intent intent = new Intent(mContext,ResultActivity.class);
intent.putExtra("MY_DATA",mDataItem); //If you want to pass data.
intent.putExtra("CLICKED_ITEM_POSTION",getAdapterPosition()); // If one want to get selected item position
startActivity(intent);*/
Toast.makeText(mContext,"You clicked item number "+ViewHolder.this.getAdapterPosition(),Toast.LENTH_SHORT).show();
}
});
}
//This is clean method to bind data with viewHolder. Do all dirty things on View based on dataItem.
//Must be called from onBindViewHolder(),with dataItem. In our case dataItem is String object.
public void bindDataWithViewHolder(DataModel dataItem){
this.mDataItem=dataItem;
if(mDataItem.isSelected()){
llView.setBackgroundColor(Color.ParseColor(SELCTED_COLOR);
}else{
llView.setBackgroundColor(Color.ParseColor(DEFAULT_COLOR);
}
//other View binding logics like setting text , loading image etc.
textView.setText(mDataItem);
}
}
}
As #Gabriel asked in comment,
what if one want to select a single item at time?
In that case, again one should not save selected item state in ViewHolder object, as the same it gets recycled and cause you problem. For that better way is have a field int selectedItemPosition in Adapter class not ViewHolder .
Following code snippet show it.
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder> {
private Context mContext;
private List<DataModel> mRViewDataList;
//variable to hold selected Item position
private int mSelectedItemPosition = -1;
public MyRecyclerViewAdapter(Context context, List<DataModel> rViewDataList) {
this.mContext = context;
this.mRViewDataList = rViewDataList;
}
#Override
public MyRecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.item_recycler_view, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.bindDataWithViewHolder(mRViewDataList.get(position),position);
}
#Override
public int getItemCount() {
return mRViewDataList != null ? mRViewDataList.size() : 0;
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView textView;
private LinearLayout llView;
private DataModel mDataItem=null;
public ViewHolder(View itemView) {
super(itemView);
llView=(LinearLayout)itemView.findViewById(R.id.ll_root_view);
textView = (TextView) itemView.findViewById(R.id.tvItemName);
cvItemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Handling for background selection state changed
int previousSelectState=mSelectedItemPosition;
mSelectedItemPosition = getAdapterPosition();
//notify previous selected item
notifyItemChanged(previousSelectState);
//notify new selected Item
notifyItemChanged(mSelectedItemPosition);
//Your other handling in onclick
}
});
}
//This is clean method to bind data with viewHolder. Do all dirty things on View based on dataItem.
//Must be called from onBindViewHolder(),with dataItem. In our case dataItem is String object.
public void bindDataWithViewHolder(DataModel dataItem, int currentPosition){
this.mDataItem=dataItem;
//Handle selection state in object View.
if(currentPosition == mSelectedItemPosition){
llView.setBackgroundColor(Color.ParseColor(SELCTED_COLOR);
}else{
llView.setBackgroundColor(Color.ParseColor(DEFAULT_COLOR);
}
//other View binding logics like setting text , loading image etc.
textView.setText(mDataItem);
}
}
}
If you only have to maintain selected Item state, I strongly discourage use of notifyDataSetChanged() method of Adapter class as RecyclerView provides much more flexibility for these cases.
You should modify your logic assign the value inside the item (object) not the view:
orderItem.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
orderItem.setSelected(xxxx);
}
});
Then in your onBindViewHolder method you have to assing the color according to this value in the item.
if (orderItem.isSelected()){
viewHolder.orderItem.setBackgroundColor(xxxx);
} else {
viewHolder.orderItem.setBackgroundColor(xxxx);
}
This is quite a common mistake that has an easy solution.
Quick answer: add this line in your onBindViewHolder method:
if (orderItem.isSelected()){
viewHolder.orderItem.setBackgroundColor(Color.parseColor(SELECTED_COLOR));
} else {
viewHolder.orderItem.setBackgroundColor(Color.parseColor(DEFAULT_COLOR));
}
(with DEFAULT_COLOR the color that the viewholder has by default)
Explained answer: when the system recycles a viewholder it just calls onBindViewHolder method so if you have changed anything of that viewholder you'll have to reset it. This will happen if you change background, item's position, etc. Any change that is not related to content per se should be reset in that method
I am creating an application. When user clicks on edit button I want to check for the user and enable editing of if the same user is the owner of the comment.
I am able to check for the user. But my problem is when user clicks on edit button of one comment, many comments become editable instead of one. I am not getting how to come out of this issue. All suggestions are welcome.
below is my adapter code:
public class ToadlineCommentAdapter extends RecyclerView.Adapter<ToadlineCommentAdapter.MyViewHolder> {
private ClickListener clickListener;
private SwipeRefreshLayout.OnRefreshListener clickListener1;
private LayoutInflater inflater;
String newEnteredCommentText, neededCommentId, text;
ArrayList<String> postCreatorId;
ArrayList<String> postIdForComments;
HttpURLConnection conn;
String userId;
int clickedPosition;
Context mContext;
ArrayList<String> commentId;
List<CommentDataStore> data = Collections.EMPTY_LIST;
private String userCredentials = "toadwaysIfocus:toadwaysIfocus";
private String basicAuth = "Basic " + new String(Base64.encode(userCredentials.getBytes(), 0));
public ToadlineCommentAdapter(Context context, List<CommentDataStore> data, ArrayList<String> commentId, ArrayList<String> postCreatorId, String userId, ArrayList<String> postIdForComments ) {
inflater = LayoutInflater.from(context);
this.data = data;
this.commentId = commentId;
mContext = context;
this.postCreatorId = postCreatorId;
this.userId = userId;
this.postIdForComments = postIdForComments;
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.custom_comment_layout, parent, false);
MyViewHolder viewHolder = new MyViewHolder(view);
return viewHolder;
}
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
CommentDataStore current = data.get(position);
Picasso.with(mContext)
.load(current.commentProfileImageLink)
.placeholder(R.mipmap.human_image)
.into(holder.commentImage);
holder.commentUserName.setText(current.commentUserName);
holder.commentDays.setText(current.commentDays);
holder.commentDescription.setText(current.commentDescription);
holder.commentAgreeCount.setText(current.commentAgreeCount);
holder.commentDisAgreeCount.setText(current.commentDisAgreeCount);
}
#Override
public int getItemCount() {
return data.size();
}
public void setClickListener(ClickListener clickListener) {
this.clickListener = clickListener;
}
public void setClickListener1(SwipeRefreshLayout.OnRefreshListener clickListener1) {
this.clickListener1 = clickListener1;
}
// View Holder object for Recycler View
class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, SwipeRefreshLayout.OnRefreshListener {
TextView commentUserName, commentDays, commentAgreeCount, commentDisAgreeCount;
EditText commentDescription;
ImageView commentImage;
ImageButton commentEdit, commentDelete;
Button commentEditSubmit;
public MyViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener((View.OnClickListener) this);
commentImage = (ImageView) itemView.findViewById(R.id.imageViewUser);
commentUserName = (TextView) itemView.findViewById(R.id.textViewTitle);
commentDays = (TextView) itemView.findViewById(R.id.textViewNoOfDays);
commentDescription = (EditText) itemView.findViewById(R.id.textViewCommentDescription);
commentAgreeCount = (TextView) itemView.findViewById(R.id.textViewAgreeCount);
commentDisAgreeCount = (TextView) itemView.findViewById(R.id.textViewDisAgreeCount);
commentEdit = (ImageButton) itemView.findViewById(R.id.imageButtonCommentEdit);
commentDelete = (ImageButton) itemView.findViewById(R.id.imageButtonCommentDelete);
commentEditSubmit = (Button) itemView.findViewById(R.id.commentEditSubmit);
commentDescription.setEnabled(false);
commentEdit.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int clickedPosition = getAdapterPosition();
if (postIdForComments.get(clickedPosition).equals(userId)){
commentDescription.setEnabled(true);
commentDescription.setBackgroundColor(mContext.getResources().getColor(R.color.colorHighlight));
commentEditSubmit.setVisibility(View.VISIBLE);
neededCommentId = commentId.get(clickedPosition);
}
else {
Toast.makeText(mContext, mContext.getResources().getString(R.string.comment_not_editable), Toast.LENGTH_SHORT).show();
return;
}
}
});
}
#Override
public void onClick(View view) {
if (clickListener != null) {
clickListener.itemClicked(view, getPosition());
}
}
#Override
public void onRefresh() {
onClick(itemView);
}
public interface ClickListener {
public void itemClicked(View view, int position);
}
}
For example if I click edit button in 5th card row I want only 5th card row to be allowed to edit. currently edit buon for 10th or 14th row will get automatically selected. Why is this happening?
The views are being recycled. In onBindViewHolder() you need to reset them to their initial state.
EDIT
RecyclerView recycles views. When one of it's children scrolls far enough off screen, the child is detached and placed in a pool of recycled/reycleable views. When the RecyclerView needs a new child to show, it can take a view from this pool (instead of instantiating from scratch) and simply bind new data to the view -- in other words, it won't call onCreateViewHolder(), but it will call onBindViewHolder().
When you click the edit button, you change the state of some views in the ViewHolder. Then you scroll until it's recycled, but it keeps all this state that it had before. You need to return these views to their initial state inside of onBindViewHolder() to handle this.
You should use SparseBooleanArray.
Look at this code and try to do this.
SparseBooleanArray mArray;
now define its size.
mArray = new SparseBooleanArray(yourArray.size());
now perform onClick
commentEdit.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int clickedPosition = getAdapterPosition();
if (postIdForComments.get(clickedPosition).equals(userId)){
mArray.put(clickedPosition,true);
}else {
Toast.makeText(mContext, mContext.getResources().getString(R.string.comment_not_editable), Toast.LENGTH_SHORT).show();
return;
}
}
});
And finally in your onBindViewHolder.
if(mArray.get(position) == true){
commentDescription.setEnabled(true);
commentDescription.setBackgroundColor(mContext.getResources().getColor(R.color.colorHighlight));
commentEditSubmit.setVisibility(View.VISIBLE);
neededCommentId = commentId.get(clickedPosition);
}else if(mArray.get(position) = true)){
}
I hope this will help you.
Edit :
You should initialize your SparseBooleanArray in your Adapter rather then ViewHolder.