My RecyclerView does not count above 10 items. After the 10th item, it shows item 9 again and then item 1 and 2.
This RecyclerView produces the bug:
mViewModel.getmPremixableIngredientsLive().observe(getViewLifecycleOwner(), new Observer<ArrayList<Ingredient>>() {
#Override
public void onChanged(ArrayList<Ingredient> premixableIngredients) {
ShowIngredientsRecyclerViewAdapter premixableComponentsRecyclerViewAdpater = new ShowIngredientsRecyclerViewAdapter(premixableIngredients);
mBinding.premixableIngredientsRecyclerview.setAdapter(premixableComponentsRecyclerViewAdpater);
GridLayoutManager gridLayoutManager = new GridLayoutManager(getContext(), 1);
mBinding.premixableIngredientsRecyclerview.setLayoutManager(gridLayoutManager);
}
});
This is the code of my adapter:
public class ShowIngredientsRecyclerViewAdapter extends RecyclerView.Adapter<ShowIngredientsRecyclerViewAdapter.ViewHolder> {
private ArrayList<Ingredient> mIngredients;
private IngredientRecyclerViewItemBinding mBinding;
public ShowIngredientsRecyclerViewAdapter(ArrayList<Ingredient> ingredients) {
mIngredients = ingredients;
}
#NonNull
#Override
public ShowIngredientsRecyclerViewAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
mBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.getContext()),
R.layout.ingredient_recycler_view_item,
parent,
false
);
return new ViewHolder(mBinding.getRoot());
}
#Override
public void onBindViewHolder(#NonNull ShowIngredientsRecyclerViewAdapter.ViewHolder holder, int position) {
mBinding.position.setText((position + 1) + ".");
mBinding.componentName.setText(mIngredients.get(position).getmComponent().getmName());
mBinding.amount.setText((mIngredients.get(position).getGrammPerCow() / 1000) + "kg/cow");
mBinding.dragBtn.setVisibility(View.GONE);
}
#Override
public int getItemCount() {
return mIngredients.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(#NonNull View itemView) {
super(itemView);
}
}
}
xml layout for the item:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:id="#+id/ingredient_recyclerview_item_linearLayout"
android:background="?android:attr/selectableItemBackground"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:paddingLeft="0dp"
android:paddingStart="0dp">
<TextView
android:id="#+id/position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1"/>
<TextView
android:id="#+id/component_name"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="Component 1"/>
<TextView
android:id="#+id/amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="50kg"/>
<ImageButton
android:id="#+id/drag_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:src="#drawable/ic_drag_grey"
android:background="#android:color/transparent"
android:layout_marginStart="8dp"/>
</LinearLayout>
</layout>
You are not implementing databinding correctly in your recyclerview. You shouldn't declare mBinding as a global variable in the adapter. Instead, it should be a global variable in the custom viewholder class. I share an example recyclerview adapter code with databinding below. You can adapt it to your case. Notice that binding is not a global variable in the adapter, but in the custom viewholder. And the viewholder accept binding as argument, so we pass binding instance to the viewholder when we create the viewholder and we use that instance when onBindViewHolder is called.
public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ProductViewHolder> {
private final List<Product> mProductList;
private final ProductItemClickListener mListener;
ProductAdapter(#NonNull List<Product> productList, ProductItemClickListener listener) {
mProductList =productList;
mListener = listener;
}
#NonNull
#Override
public ProductViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
ItemProductBinding binding = DataBindingUtil
.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_product,
parent, false);
return new ProductViewHolder(binding);
}
#Override
public void onBindViewHolder(#NonNull ProductViewHolder holder, int position) {
holder.bind(mProductList.get(position), mListener);
}
#Override
public int getItemCount() {
return mProductList.size();
}
class ProductViewHolder extends RecyclerView.ViewHolder{
final ItemProductBinding binding;
ProductViewHolder(ItemProductBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
void bind(Product currentProduct, ProductItemClickListener clickListener){
//For each item, corresponding product object is passed to the binding
binding.setProduct(currentProduct);
binding.setProductItemClick(clickListener);
//This is to force bindings to execute right away
binding.executePendingBindings();
}
}
public interface ProductItemClickListener {
void onProductItemClicked(Product product);
}
}
Related
I making a list of usernames and call buttons, each username has a call button next to it, when I click the call button the code inside the call button does not get executed in all clicks, I tried to make the whole item clickable so that if the user clicks on the button, the username or any part in the layout_item the code gets executed but no change happened.
Here is how I am implementing the adapter:
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View mView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.freind_list_item, parent,false);
return new MyViewHolder(mView);
}
#Override
public void onBindViewHolder(#NonNull MyViewHolder holder, int position) {
Friend friend = friends.get(position);
holder.btnInitCallHandler(friend.getUsername(), friend.get_id());
holder.setUsername(friend.getUsername());
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView txtUsername;
Button btnInitCallWithFriend;
RelativeLayout rlInsteadOfButtonHandler;
MyViewHolder(View itemView) {
super(itemView);
txtUsername = itemView.findViewById(R.id.txt_friend_item);
btnInitCallWithFriend = itemView.findViewById(R.id.btnInitCallWithFriendNormal);
friendState = itemView.findViewById(R.id.onlineState);
signedAsGlasses = itemView.findViewById(R.id.signedInAsGlasses);
signedAsLaptop = itemView.findViewById(R.id.signedInAsLaptop);
rlInsteadOfButtonHandler = itemView.findViewById(R.id.rlInsteadButtonHandler);
}
void setUsername(String username) {
txtUsername.setText(username);
}
void btnInitCallHandler(String username, String userId) {
btnInitCallWithFriend.setOnClickListener((view) -> {
Timber.tag("KingArmstringNormalUserCall").d("normal user call button clicked from the adapter");
viewAccessor.initCall(username, userId);
});
rlInsteadOfButtonHandler.setOnClickListener((v) -> {
viewAccessor.initCall(username, userId);
});
}
}
the viewAccessor is just an interface implemented by the activity, and have only one method (initCall)
the initCall(username, userId) in the Activity has only a toast and a log
EDIT:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="100dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:background="#android:color/black"
android:padding="10dp">
<TextView
android:id="#+id/txt_friend_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/txt_friend_name_placeholder"
android:textSize="30sp"
android:layout_gravity="center"
android:gravity="center"
android:textColor="#color/colorAccent"/>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<RelativeLayout
android:id="#+id/rlInsteadButtonHandler"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<!--<at.markushi.ui.CircleButton-->
<!--android:id="#+id/btnInitCallWithFriend"-->
<!--android:layout_width="70dp"-->
<!--android:layout_height="40dp"-->
<!--android:src="#drawable/ic_video_call"-->
<!--android:layout_marginStart="10dp"-->
<!--android:layout_gravity="center"-->
<!--android:focusable="false"-->
<!--android:focusableInTouchMode="false"-->
<!--app:cb_color="#fff"-->
<!--android:layout_alignParentEnd="true"/>-->
<Button
android:id="#+id/btnInitCallWithFriendNormal"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_gravity="center"
android:layout_marginStart="10dp"
android:layout_marginTop="0dp"
android:layout_marginBottom="0dp"
android:focusable="true"
android:text="call" />
</RelativeLayout>
use below code on constructor of MyViewHolder class. This code makes an item clickable.
itemView.setClickable(true);
Also set onClickLiester on MyViewHolder class. Then use getAdapterPostion() method to determine which item clicked.
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dataList.get(getAdapterPosition()); //this code will return the clicked item object.
});
First remove btnInitCallHandler() function from your ViewHolder, then set the clickListener directly in your viewHolder, you can get the current item from your list by calling getAdapterPosition() for exemple:
btnInitCallWithFriend.setOnClickListener((view) -> {
Timber.tag("KingArmstringNormalUserCall").d("normal user call button clicked from the adapter");
viewAccessor.initCall(friends.get(getAdapterPosition).getUsername(), friends.get(getAdapterPosition).get_id());
});
so this your new adapter class:
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View mView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.freind_list_item, parent,false);
return new MyViewHolder(mView);
}
#Override
public void onBindViewHolder(#NonNull MyViewHolder holder, int position) {
//do your stuff
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView txtUsername;
Button btnInitCallWithFriend;
RelativeLayout rlInsteadOfButtonHandler;
MyViewHolder(View itemView) {
super(itemView);
txtUsername = itemView.findViewById(R.id.txt_friend_item);
btnInitCallWithFriend = itemView.findViewById(R.id.btnInitCallWithFriendNormal);
friendState = itemView.findViewById(R.id.onlineState);
signedAsGlasses = itemView.findViewById(R.id.signedInAsGlasses);
signedAsLaptop = itemView.findViewById(R.id.signedInAsLaptop);
rlInsteadOfButtonHandler = itemView.findViewById(R.id.rlInsteadButtonHandler);
btnInitCallWithFriend.setOnClickListener((view) -> {
Timber.tag("KingArmstringNormalUserCall").d("normal user call button clicked from the adapter");
viewAccessor.initCall(friends.get(getAdapterPosition).getUsername(), friends.get(getAdapterPosition).get_id());
});
rlInsteadOfButtonHandler.setOnClickListener((v) -> {
viewAccessor.initCall(friends.get(getAdapterPosition).getUsername(), friends.get(getAdapterPosition).get_id());
});
}
void setUsername(String username) {
txtUsername.setText(username);
}
}
Following is a simple yet powerful implementation of RecylcerView. Try to read out the basics of recycler view and why viewholder is used. Hope this helps you out.
public class TestActivity extends Activity implements TestAdapter.ItemClickListener {
TestAdapter adapter;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test_layout);
ArrayList<String> testNames = new ArrayList<>();
testNames.add("Horse");
testNames.add("Cow");
testNames.add("Camel");
testNames.add("Sheep");
testNames.add("Goat");
RecyclerView recyclerView = findViewById(R.id.testList);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new TestAdapter(this, testNames);
adapter.setClickListener(this);
recyclerView.setAdapter(adapter);
}
#Override
public void onItemClick(View view, int position) {
Toast.makeText(this, "You clicked " + adapter.getItem(position) + " on row number " + position, Toast.LENGTH_SHORT).show();
}}
Following is adapter class implementation
public class TestAdapter extends RecyclerView.Adapter<TestAdapter.MyViewHolder> {
private ItemClickListener mClickListener;
private List<String> mData;
private Activity mActivity;
public TestAdapter(TestActivity testActivity, ArrayList<String> testNames) {
this.mData = testNames;
this.mActivity = testActivity;
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View mView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.test_row, parent, false);
return new MyViewHolder(mView);
}
#Override
public void onBindViewHolder(#NonNull MyViewHolder holder, int position) {
String animal = mData.get(position);
holder.txtUsername.setText(animal);
}
#Override
public int getItemCount() {
return mData.size();
}
public String getItem(int position) {
return mData.get(position);
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView txtUsername;
Button btnInitCallWithFriend;
MyViewHolder(View itemView) {
super(itemView);
txtUsername = itemView.findViewById(R.id.txt_friend_item);
btnInitCallWithFriend = itemView.findViewById(R.id.btnInitCallWithFriendNormal);
//friendState = itemView.findViewById(R.id.onlineState);
//signedAsGlasses = itemView.findViewById(R.id.signedInAsGlasses);
//signedAsLaptop = itemView.findViewById(R.id.signedInAsLaptop);
btnInitCallWithFriend.setOnClickListener((View view) -> {
Toast.makeText(mActivity, "You clicked call " + getAdapterPosition() + " position", Toast.LENGTH_SHORT).show();
});
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (mClickListener != null) {
mClickListener.onItemClick(itemView, getAdapterPosition());
}
}
});
}
}
// allows clicks events to be caught
void setClickListener(ItemClickListener itemClickListener) {
this.mClickListener = itemClickListener;
}
// parent activity will implement this method to respond to click events
public interface ItemClickListener {
void onItemClick(View view, int position);
}}
I have finally figured out whats happening, I have been handling lots of things in the foreground, I have transferred lots of them to other threads, now it worked, u might don't believe me, but many really experienced people said to me, that they encountered a very similar situations.
Ok I'll try one more time. Last time I asked about passing data between recyclerview and item and one person helped me with open item by click, but I still don't have idea how to show the data of clicked item in new activity. I want to click on an item and then display the data of that item in new activity. In this activity I want to edit data.
Does anyone knows how to do it? I need any idea.
RecyclerView Adapter with OnItemClickListener interface:
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.TaskViewHolder> {
private List<MainViewModel> mTasks;
private List<Task> tasks = new ArrayList<>();
private Context context;
private EditTaskViewModel editTaskViewModel;
public RecyclerViewAdapter(List<MainViewModel> tasks, Context context, EditTaskViewModel editTaskViewModel) {
this.mTasks = tasks;
this.context = context;
this.editTaskViewModel = editTaskViewModel;
}
#NonNull
#Override
public RecyclerViewAdapter.TaskViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
final RecyclerViewItemBinding binding = DataBindingUtil.inflate(inflater, R.layout.recycler_view_item, parent, false);
binding.setItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(View view) {
Intent intent = new Intent(view.getContext(), EditTaskActivity.class);
intent.putExtra("id", binding.getPosition());
view.getContext().startActivity(intent);
Toast.makeText(view.getContext(), "ID " + binding.getPosition(), Toast.LENGTH_SHORT).show();
}
});
return new TaskViewHolder(binding);
}
#Override
public void onBindViewHolder(#NonNull final RecyclerViewAdapter.TaskViewHolder holder, final int position) {
Task currentTask = tasks.get(position);
holder.mBinding.descriptionItem.setText(currentTask.getDescription());
holder.mBinding.dateItem.setText(currentTask.getDate());
holder.mBinding.timeItem.setText(currentTask.getTime());
holder.mBinding.setPosition(position);
}
#Override
public int getItemCount() {
return tasks.size();
}
public void setTasks(List<Task> tasks) {
this.tasks = tasks;
notifyDataSetChanged();
}
public Task getTaskPosition(int position) {
return tasks.get(position);
}
public class TaskViewHolder extends RecyclerView.ViewHolder {
private final RecyclerViewItemBinding mBinding;
public TaskViewHolder(RecyclerViewItemBinding binding) {
super(binding.getRoot());
this.mBinding = binding;
}
public void bind (MainViewModel mainViewModel){
mBinding.setItemView(mainViewModel);
mBinding.executePendingBindings();
}
}
public interface OnItemClickListener {
void onItemClick(View view);
}
Item XML file:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="itemView"
type="com.example.daniellachacz.taskmvvm.viewmodel.MainViewModel">
</variable>
<variable
name="itemClickListener"
type="com.example.daniellachacz.taskmvvm.adapter.RecyclerViewAdapter.OnItemClickListener">
</variable>
<variable
name="task"
type="com.example.daniellachacz.taskmvvm.model.Task">
</variable>
<variable
name="position"
type="int">
</variable>
</data>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="120dp"
android:shadowColor="#color/colorPrimary"
android:backgroundTint="#color/cardview_shadow_end_color">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="110dp"
android:layout_marginBottom="6dp"
android:layout_marginTop="6dp"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp"
android:onClick="#{(view)-> itemClickListener.onItemClick(view)}">
<TextView
android:id="#+id/description_item"
android:layout_width="250dp"
android:layout_height="96dp"
android:layout_marginStart="5dp"
android:layout_marginTop="9dp"
android:layout_marginBottom="5dp"
android:text="#{itemView.description}"
android:textSize="18sp"
android:textColor="#020202"
android:focusable="true" />
<TextView
android:id="#+id/date_item"
android:layout_width="90dp"
android:layout_height="40dp"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="9dp"
android:layout_marginEnd="10dp"
android:gravity="center"
android:text="#{itemView.date}"
android:textColor="#020202"
android:textSize="16sp" />
<TextView
android:id="#+id/time_item"
android:layout_width="90dp"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:layout_alignStart="#+id/date_item"
android:layout_marginBottom="10dp"
android:layout_marginEnd="10dp"
android:gravity="center"
android:text="#{itemView.time}"
android:textColor="#020202"
android:textSize="16sp" />
</RelativeLayout>
</android.support.v7.widget.CardView>
</layout>
onCreate:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
floatingActionButton = findViewById(R.id.floating_action_button);
List<Task> tasks = new ArrayList<>();
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setHasFixedSize(true);
final RecyclerViewAdapter recyclerViewAdapter = new RecyclerViewAdapter(context, tasks);
recyclerView.setAdapter(recyclerViewAdapter);
mainViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
mainViewModel.getAllTasks().observe(this, recyclerViewAdapter::setTasks);
Here are a couple of suggestions you might find useful:
Don't depend on ViewModels in your adapters. ViewModels are meant to handle events from views (Fragments or Activities) and broadcast updates back to the views via some observable mechanism (most commonly LiveData instances). Referencing your ViewModels directly inside an adapter is bad, since it couples them together. This means that it will be very hard for you to reuse your adapter with a different ViewModel if needed. I know it doesn't seem likely at this point in time, but just trust me on this one. After the changes have been applied, your adapter should look something like this:
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.TaskViewHolder> {
private LayoutInflater mLayoutInflater;
private List<Task> mTasks;
private OnItemClickListener mOnItemClickListener;
public RecyclerViewAdapter(#NonNull Context context, #NonNull List<Task> tasks) {
mLayoutInflater = LayoutInflater.fromContext(context);
mTasks = tasks;
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
mOnItemClickListener = onItemClickListener;
}
#NonNull
#Override
public RecyclerViewAdapter.TaskViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
final RecyclerViewItemBinding binding = DataBindingUtil.inflate(mLayoutInflater, R.layout.recycler_view_item, parent, false);
return new TaskViewHolder(binding);
}
#Override
public void onBindViewHolder(#NonNull final RecyclerViewAdapter.TaskViewHolder holder, final int position) {
Task currentTask = tasks.get(position);
holder.bind(currentTask, mOnItemClickListener);
}
#Override
public int getItemCount() {
return tasks.size();
}
public void setTasks(List<Task> tasks) {
this.tasks = tasks;
notifyDataSetChanged();
}
public Task getTaskPosition(int position) {
return tasks.get(position);
}
public class TaskViewHolder extends RecyclerView.ViewHolder {
private final RecyclerViewItemBinding mBinding;
public TaskViewHolder(RecyclerViewItemBinding binding) {
super(binding.getRoot());
this.mBinding = binding;
}
public void bind (Task item, OnItemClickListener onItemClickListener) {
mBinding.setItem(item);
mBinding.executePendingBindings();
itemView.setOnClickListener(view -> {
if (onItemClickListener != null) {
onItemClickListener.onItemClick(view, item);
}
}
}
}
public interface OnItemClickListener {
void onItemClick(View view, Task item);
}
}
The OnItemClickListener.onItemClick() method now passes the view and the item itself as parameters. This is the easiest way to expose the clicked item to whoever might be interested. The on click listener is now set at the adapter level, using setOnItemClickListener().
The setting of the OnClickListener of the item view is now done in the bind() method of the TaskViewHolder. When binding, we know the exact item that is going to populate the view, so we can return it to the OnItemClickListener.
You have to simplify the layout as well, since there are a lot of things that are not really needed. It may look like this:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="task"
type="com.example.daniellachacz.taskmvvm.model.Task">
</variable>
</data>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="120dp"
android:shadowColor="#color/colorPrimary"
android:backgroundTint="#color/cardview_shadow_end_color">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="110dp"
android:layout_marginBottom="6dp"
android:layout_marginTop="6dp"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp">
<TextView
android:id="#+id/description_item"
android:layout_width="250dp"
android:layout_height="96dp"
android:layout_marginStart="5dp"
android:layout_marginTop="9dp"
android:layout_marginBottom="5dp"
android:text="#{item.description}"
android:textSize="18sp"
android:textColor="#020202"
android:focusable="true" />
<TextView
android:id="#+id/date_item"
android:layout_width="90dp"
android:layout_height="40dp"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="9dp"
android:layout_marginEnd="10dp"
android:gravity="center"
android:text="#{item.date}"
android:textColor="#020202"
android:textSize="16sp" />
<TextView
android:id="#+id/time_item"
android:layout_width="90dp"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:layout_alignStart="#+id/date_item"
android:layout_marginBottom="10dp"
android:layout_marginEnd="10dp"
android:gravity="center"
android:text="#{item.time}"
android:textColor="#020202"
android:textSize="16sp" />
</RelativeLayout>
</android.support.v7.widget.CardView>
</layout>
The only variable is the item and we are binding it's properties to the TextViews.
I guess this should be enough to get you going.
Just a couple of other things that are not directly related to the question, but are important.
Null safety - you never check the input when calling setTask() in the adapter. A client may pass null and cause crashes all over the place. You should try and prevent that.
Calling notifyDataSetChanged() is not a good practice when working with RecyclerView.Adapter since this will cancel all the built-in animations of the RecyclerView. It's better to use the other notify... methods. You might want to check DiffUtil at some point.
It depends on how your data is stored and if it accessible in the second activity. If you have a static ArrayList of your data you can pull the data from it using the index you passed in your onclick of the RV item. For example:
class myData{
private ArrayList<Data> myDataArray;
static ArrayList<Data> getMyDataArray(){
return myDataArray;
}
static void setMyDataArray(array)
myDataArray = array;
}
So you fill your RV with getMyDataArray() then you set the onlclick to send the index clicked in the RV to the next activity.
In onLoad of the second activity:
int myDataIndex = getIntent().getIntExtra("id",0);
Data myData = myData.getMyDataArray().get(myDataIndex);
Note: Data is whatever your data is, could be strings, or ints, or a custom class/object with data.
Here is what I have done for item click listener in recycler view using data binding.
ADAPTER CODE
public class TC_DashboardRecViewAdapter extends RecyclerView.Adapter<TC_DashboardRecViewAdapter.ViewHolder> {
Context context;
List<String> list;
private TcDashboardItemBinding tcDashboardItemBinding;
ItemClickListener itemClickListener;
public TC_DashboardRecViewAdapter(Context context, List<String> dashboardItems, ItemClickListener itemClickListener) {
this.context = context;
this.list = dashboardItems;
this.itemClickListener = itemClickListener;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
ViewDataBinding binding = DataBindingUtil.inflate(inflater, R.layout.tc_dashboard_item, parent, false);
tcDashboardItemBinding = (TcDashboardItemBinding) parent.getTag();
return new ViewHolder(binding);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
holder.bind(list.get(position), itemClickListener, position);
}
#Override
public int getItemCount() {
return list.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
private ViewDataBinding binding;
public ViewHolder(ViewDataBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(String s, ItemClickListener itemClickListener, int position) {
this.binding.setVariable(BR.itemModel, s);
this.binding.executePendingBindings();
binding.getRoot().setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(itemClickListener !=null){
itemClickListener.onItemClicked(binding.getRoot(), s, position);
}
}
});
}
}
ITEM CLICK INTERFACE:
public interface ItemClickListener {
void onItemClicked(View vh, Object item, int pos);
}
FRAGMENT/ACTIVITY CODE:
public class TC_DashboardFragment extends BaseFragment implements ItemClickListener {
public void onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
tc_dashboardRecViewAdapter = new TC_DashboardRecViewAdapter(getContext(), getDashboardItems(), this);
linearLayoutManager = new LinearLayoutManager(getContext());
dashboardrecyclerview.setLayoutManager(new LinearLayoutManager(getContext()));
binding.setAdapter(tc_dashboardRecViewAdapter);
}
#Override
public void onItemClicked(View vh, Object item, int pos) {
Toast.makeText(mainActivity, item.toString(), Toast.LENGTH_SHORT).show();
}
When you run the code and click on recycler view item it would show a toast with the text from the item clicked.
I am creating simple screen which has one textview and button with the use of Recyclerview and Cardview.
Xml code is ready but I dont know what to write in my OnBindViewHolder class which is in my Adapter.
This is My Adapter for RecycleView
public class PaperAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<String> papers;
public TextView paperName;
public PaperAdapter(List<String> papers) {
this.papers = papers;
}
public class MyViewHolder extends RecyclerView.ViewHolder{
public MyViewHolder(View itemView) {
super(itemView);
paperName = itemView.findViewById(R.id.tvPaperName);
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View paperView = layoutInflater.inflate(R.layout.paper_row, parent, false);
MyViewHolder myViewHolder = new MyViewHolder(paperView);
return myViewHolder;
}
#Override
public void onBindViewHolder( RecyclerView.ViewHolder holder, int position) {
}
#Override
public int getItemCount() {
return papers.size();
}}
This is my MainActivity
public class MainActivity extends AppCompatActivity {
RecyclerView recyclerView;
List<String> papers = new ArrayList<>();
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_paper);
papers.add("Paper 1");
papers.add("Paper 2");
papers.add("Paper 3");
papers.add("Paper 4");
recyclerView = findViewById(R.id.rvPapers);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
PaperAdapter adapter = new PaperAdapter(papers);
recyclerView.setAdapter(adapter);
}
}
This is my Pagelayout xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="4dp"
app:cardElevation="5dp"
app:cardBackgroundColor="#A4C639"
app:cardUseCompatPadding="true"
>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp"
>
<TextView
android:id="#+id/tvPaperName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:text="paper 1"
android:textColor="#000000"
android:textSize="20sp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:text="download" />
</RelativeLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
App is working without writing anything in OnBindViewHolder but its not showing Paper1,Paper2,Paper3 like this list so thats why I need to define textview in OnBindViewHolder but its showing error.Please let me know how to define textview in that and what to write in OnBindViewHolder.Also wanna set OnCLickListner for button so help me in that too.Thank you
onBindViewHolder(ViewHolder, int) is called by RecyclerView to display the data at the specified position. This method should update the contents of the RecyclerView.ViewHolder.itemView to reflect the item at the given position.
So you need to use this method to update the contents of the itemView to reflect the item at the given position.
As per your question you want to show TextView with Button in your RecyclerView, so you need to implement onBindViewHolder() as:
#Override
public void onBindViewHolder( RecyclerView.ViewHolder holder, int position) {
holder.paperName.setText(papers.get(position));
holder.download.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// TODO: Your code to download
}
});
}
You also need to give id to your button as:
<Button
android:id="#+id/btnDownload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:text="download" />
And update MyViewHolder as:
public class MyViewHolder extends RecyclerView.ViewHolder{
TextView paperName;
Button download;
public MyViewHolder(View itemView) {
super(itemView);
paperName = itemView.findViewById(R.id.tvPaperName);
download = itemView.findViewById(R.id.btnDownload);
}
}
Simply, copy and paste this code in your PageAdapter class:
public class PaperAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<String> papers;
private Context mContext;
public PaperAdapter(Context context, List<String> papers) {
this.papers = papers;
this.mContext = context;
}
public class MyViewHolder extends RecyclerView.ViewHolder{
TextView paperName;
Button download;
public MyViewHolder(View itemView) {
super(itemView);
paperName = itemView.findViewById(R.id.tvPaperName);
download = itemView.findViewById(R.id.btnDownload);
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View paperView = layoutInflater.inflate(R.layout.paper_row, parent, false);
MyViewHolder myViewHolder = new MyViewHolder(paperView);
return myViewHolder;
}
#Override
public void onBindViewHolder( RecyclerView.ViewHolder holder, int position) {
holder.paperName.setText(papers.get(position));
holder.download.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Toast.makeText(mContext, papers.get(position), Toast.LENGTH_SHORT).show();
// TODO: Your code to download
}
});
}
#Override
public int getItemCount() {
return papers.size();
}
}
EDIT: You need Context to show Toast, so pass Context in your MainActivity as:
PaperAdapter adapter = new PaperAdapter(this, papers);
I need to access a method of RecyclerView Adapter from View Holder. I don't find any solution for this.
Or is it possible to access adpater'method(get an instance of adapter) from ViewModel class (I've defined a viewModel for items of RecyclerView in MVVM pattern) of ViewHolder.
Regards,
Habib
Thanks to all, here is code snippets. in ItemViewModel class I want to get adapter's methods.
Adapter && ViewHolder :
public class MoviesAdapter extends RecyclerView.Adapter<MoviesAdapter.MyViewHolder> {
public class MyViewHolder extends RecyclerView.ViewHolder {
........
public MyViewHolder(View view) {
super(view);
......
}
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false);
return itemView;
}
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
........
}
#Override
public int getItemCount() {
.........
}
}
list_item.xml :
<?xml version="1.0" encoding="utf-8"?>
<data>
<variable
name="viewModel"
type="program.viewmodel.ItemViewModel" />
</data>
<android.support.v7.widget.CardView
android:id="#+id/cvMain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{viewModel.onClickCard()}"
android:onLongClick="#{viewModel.onLongClickCard()}" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:paddingLeft="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/engM"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:paddingLeft="5dp"
android:text="#{viewModel.engligh}"
android:textSize="20sp" />
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
ItemViewModel class :
public class ItemViewModel{
private Proverb proverb; //Proverb is model class
public ItemViewModel(Proverb item) {
this.proverb = item;
notifyChange();
}
#Bindable
public String getEngligh() {
return proverb.getEngligh();
}
/**
* click each item
* #return
*/
public View.OnClickListener onClickProverb() {
return new View.OnClickListener() {
#Override
public void onClick(View view) {
// here I need to access adapter's method
}
};
}
/**
* long click listener
* #return
*/
public View.OnLongClickListener onLongClickProverb() {
return new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
// here I need to access adapter's method
return true;
}
};
}
}
I've got a RecyclerView which populates from an ArrayList. The output is a CardView layout.
In the Cardview, there are 2 buttons amongst other Views.
They only have to read the current value of a TextView, which by default is 1, and increase or decrease it.
The Arraylist contains 8 items.
When I run the app the UI works fine. Trouble is when I try to modify the value of the TextView.
The value is correctly increased and decreased on the CardView I'm working on, but ALSO the value is modified on another CardView. And in that second CardView, modifying its TextView value, also modifies the first one.
So, what am I doing wrong?
This is my Fragment:
public class Fragment_rosas extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.layout_rosas,container,false);
RecyclerView recyclerview_rosas;
RecyclerView.Adapter adaptador_rv_rosas;
RecyclerView.LayoutManager lm_rosas;
List rosas = new ArrayList();
rosas.add(new Tropa(1,R.drawable.minibarbaro, getResources().getString(R.string.barbaro),7,1));
recyclerview_rosas = (RecyclerView) view.findViewById(R.id.recyclerView_tropasRosas);
recyclerview_rosas.setHasFixedSize(true);
lm_rosas = new LinearLayoutManager(getContext());
recyclerview_rosas.setLayoutManager(lm_rosas);
adaptador_rv_rosas = new AdaptadorTropa(rosas);
recyclerview_rosas.setAdapter(adaptador_rv_rosas);
return view;
}
}
And here the part of code on my Adapter:
#Override
public void onBindViewHolder(final TropaViewHolder viewHolder, int i) {
viewHolder.imagen.setImageResource(items.get(i).getImagen());
viewHolder.nombre.setText(items.get(i).getNombre());
viewHolder.maxnivel.setText(String.valueOf(items.get(i).getNivelMax()));
viewHolder.espacioencamp.setText((String.valueOf(items.get(i).getEspacioEnCamp())));
final String nombre = items.get(i).getNombre();
final int maxnivel = items.get(i).getNivelMax();
viewHolder.nivelmas.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String niveltemp = viewHolder.nivel.getText().toString();
String nivelmaxtemp = viewHolder.maxnivel.getText().toString();
int nivel = Integer.parseInt(niveltemp);
int maxxnivel = Integer.parseInt(nivelmaxtemp);
int nuevonivel = nivel+1 ;
if (nuevonivel<=maxxnivel) {
viewHolder.txtv_nivel.setText(String.valueOf(nuevonivel));
}
}
});
My OnCreateViewHolder (nothing really happens here):
#Override
public TropaViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.cardview, viewGroup, false);
return new TropaViewHolder(v);
}
Here is the solution, as mentioned in the comment above, it addresses two problems:
1. positiontoValueMap - saves current value for each position
2. onclicklistener is passed to the ViewHolder in onCreateViewHolder
Adapter Class
public class MyAdapter extends RecyclerView.Adapter {
private Context context;
private List<String> dataList;
private Map<Integer, Integer> positionToValueMap = new HashMap<>();
public MyAdapter(Context context, List<String> dataList) {
this.context = context;
this.dataList = dataList;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.recycler_view_item, null, false);
return new MyViewHolder(view, new OnRecyclerItemClickListener());
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
((MyViewHolder) holder).onRecyclerItemClickListener.updatePosition(position);
((MyViewHolder) holder).position.setText("" + position);
((MyViewHolder) holder).title.setText(dataList.get(position));
int valueToDisplay = 1;
if(positionToValueMap.containsKey(position)) {
valueToDisplay = positionToValueMap.get(position);
} else {
positionToValueMap.put(position, valueToDisplay);
}
((MyViewHolder) holder).valueView.setText("value: " + valueToDisplay);
}
#Override
public int getItemCount() {
return dataList.size();
}
private class MyViewHolder extends RecyclerView.ViewHolder {
private OnRecyclerItemClickListener onRecyclerItemClickListener;
private TextView position;
private TextView title;
private TextView valueView;
public MyViewHolder(View itemView, OnRecyclerItemClickListener onRecyclerItemClickListener) {
super(itemView);
itemView.setOnClickListener(onRecyclerItemClickListener);
this.onRecyclerItemClickListener = onRecyclerItemClickListener;
this.position = (TextView) itemView.findViewById(R.id.position);
this.title = (TextView) itemView.findViewById(R.id.title);
this.valueView = (TextView) itemView.findViewById(R.id.value_view);
}
}
private class OnRecyclerItemClickListener implements View.OnClickListener {
private int position = -1;
public void updatePosition(int position) {
this.position = position;
}
#Override
public void onClick(View v) {
int oldValue = positionToValueMap.get(position); // get current value
oldValue++; // increment
positionToValueMap.put(position, oldValue); // save current value
notifyItemChanged(position); // update clicked view so that it picks up the new saved value from the positionToValueMap in onBindViewHolder
}
}
}
RecyclerView item layout
<TextView
android:id="#+id/position"
android:layout_width="30dp"
android:layout_height="50dp"
android:textColor="#android:color/white"
android:gravity="center"
android:background="#android:color/holo_green_light"
android:layout_alignParentLeft="true"/>
<TextView
android:id="#+id/title"
android:layout_width="50dp"
android:layout_height="50dp"
android:textColor="#android:color/white"
android:gravity="center"
android:background="#android:color/holo_green_dark"
android:layout_toRightOf="#id/position" />
<TextView
android:id="#+id/value_view"
android:layout_width="match_parent"
android:layout_height="50dp"
android:textColor="#android:color/white"
android:gravity="center"
android:background="#android:color/holo_green_light"
android:layout_toRightOf="#id/title"
android:layout_alignParentRight="true"/>
</RelativeLayout>
And Activity to test it out
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
recyclerView.setAdapter(new MyAdapter(getApplicationContext(), getSampleData()));
}
private static List<String> getSampleData() {
List<String> dataList = new ArrayList<>();
dataList.add("zero");
dataList.add("one");
dataList.add("two");
dataList.add("three");
dataList.add("four");
dataList.add("five");
dataList.add("six");
dataList.add("seven");
dataList.add("eight");
dataList.add("nine");
dataList.add("ten");
dataList.add("eleven");
dataList.add("twelve");
dataList.add("thirteen");
dataList.add("fourteen");
dataList.add("fifteen");
dataList.add("sixteen");
dataList.add("seventeen");
dataList.add("eighteen");
dataList.add("nineteen");
dataList.add("twenty");
return dataList;
}
}
activity layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white"/>
</RelativeLayout>