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;
}
};
}
}
Related
Here's my Fragment:
public class Fragment_Cities extends Fragment implements Adapter_Cities.CitiesListener {
private Adapter_Cities adapterCities;
private FragmentCitiesBinding binding;
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
binding = FragmentCitiesBinding.inflate(inflater, container, false);
return binding.getRoot();
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
adapterCities = new Adapter_Cities(this);
binding.recyclerView.setAdapter(adapterCities);
ViewModel_Cities viewModelCities = new ViewModelProvider(this).get(ViewModel_Cities.class);
viewModelCities.getLiveDataCities().observe(getViewLifecycleOwner(), model_cities ->
adapterCities.setAdapterData(model_cities));
}
#Override
public void onCitySelected(Model_Cities city) {
if (!alertDialog.isShowing()) alertDialog.show();
new DialogFragment_Map().newInstance(city.getName(), city.getLatLon(), this).
show(requireActivity().getSupportFragmentManager(), null);
}
}
**
Here's my Adapter:
**
public class Adapter_Cities extends RecyclerView.Adapter<Adapter_Cities.MyViewHolder> implements Filterable {
private List<Model_Cities> cityList, cityListFiltered;
public CitiesListener citiesListener;
public class MyViewHolder extends RecyclerView.ViewHolder {
private final RowCityBinding rowCityBinding;
public MyViewHolder(View itemView) {
super(itemView);
rowCityBinding = RowCityBinding.bind(itemView);
// itemView.setOnClickListener(v -> recyclerViewItemClickListener.onCitySelected(cityListFiltered.get(getBindingAdapterPosition())));
}
}
public Adapter_Cities(CitiesListener citiesListener) {
this.citiesListener = citiesListener;
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_city, parent, false);
return new MyViewHolder(itemView);
}
#Override
public void onBindViewHolder(Adapter_Cities.MyViewHolder holder, int position) {
holder.rowCityBinding.setModel(cityListFiltered.get(position));
holder.rowCityBinding.executePendingBindings();
}
public void setAdapterData(List<Model_Cities> cityList) {
this.cityList = cityList;
this.cityListFiltered = cityList;
notifyDataSetChanged();
}
#Override
public int getItemCount() {
return cityListFiltered == null ? 0 : cityListFiltered.size();
}
public interface CitiesListener {
void onCitySelected(Model_Cities city);
}
}
**
Here's my XML Layout:
**
<data>
<variable
name="callback"
type="com.base.assignment.adapters.Adapter_Cities.CitiesListener" />
<variable
name="model"
type="com.base.assignment.models.Model_Cities" />
</data>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="15dp"
android:background="#color/white"
android:onClick="#{()->callback.onCitySelected(model)}">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{model.title}"
android:textColor="#color/black"
android:textSize="15sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{model.subTitle}"
android:textColor="#color/black"
android:textSize="12sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
**
Here's the result i got:
**
My actual requirement is when user clicks on RecyclerView Item, the app must show a DialogFragment which should be called by a Fragment. So, I'm trying to trigger onClick of RecyclerView layout through XML, and the action that should happen on clicking is -> The information of clicked item should be sent to Fragment with Model Object as it's parameter.
Whereas When I clicked on Recyclerview Item no action is been happening. No crashes, no information in logcat, no updates in App UI.
As DataBinding shows the Compile time errors, i am seeing the below errors:
And right to it, i see the code details of the issue (as attached below):
For that set your listener interface for layout within onBindViewHolder().
#Override
public void onBindViewHolder(Adapter_Cities.MyViewHolder holder, int position) {
holder.rowCityBinding.setCallback(citiesListener);
holder.rowCityBinding.setModel(cityListFiltered.get(position));
holder.rowCityBinding.executePendingBindings();
}
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);
}
}
I am creating Android app, using room database.
I have two tables DogsTable:
#PrimaryKey(autoGenerate = true)
int dog_id;
String dogName;
and CatsTable (both tables have constructor and getter methods ):
#PrimaryKey(autoGenerate = true)
int cat_id;
String catName;
1- How to display in one RecyclerView two different object type
ArrayList<DogsTable> dog_list;
ArrayList<CatsTable> cat_list;
I am getting the values of dog_list and cat_list from ViewModel Query as show in MainActivity.class.
2- How to fix getItemCount() method? I don't know how to return two different object cat_list.size(); and dog_list.size();
3- Also in onBindViewHolder() method I don`t know how to get cat_list values to display them in UI?
4- Another problem is in swapToDelete() Method in MainActivity.class, I can get the dog id to delete it, but I can not get the cat id to delete it, how can I get the cat id ?
5- How can I display (dog1,dog2 , dog3) as show in first image? (i inserted the value manually in the first image just to show how i want to display them )
Existing Output as below:
My code
MainActivity.java
public class MainActivity extends AppCompatActivity implements MainActivityAdapter.ItemClickListener {
MyViewModel viewModel;
MainActivityAdapter adapter;
RecyclerView recyclerView;
LinearLayoutManager layoutManager;
Button btn_addDog, btn_addCat;
EditText et_addDogName, et_addCatName;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
actionButton();
buildRecyclerView();
setUpViewModel_dogs();
swapToDelete_dog();
}
private void initViews() {
et_addDogName = findViewById(R.id.addDogNameET_xml);
et_addCatName = findViewById(R.id.addCatNameET_xml);
}
public void actionButton() {
btn_addDog = findViewById(R.id.AddDog_btn_xml);
btn_addCat = findViewById(R.id.AddCat_btn_xml);
btn_addDog.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
insertDog();
}
});
btn_addCat.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
insertCat();
}
});
}
private void buildRecyclerView() {
recyclerView = findViewById(R.id.recyclerView_id);
adapter = new MainActivityAdapter(this, this);
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
}
// Query
public void setUpViewModel_dogs() {
viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
viewModel.getAllDogs().observe(this, new Observer<List<DogsTable>>() {
#Override
public void onChanged(#Nullable List<DogsTable> dogsTables) {
adapter.setDog_list((ArrayList<DogsTable>) dogsTables);
}
});
}
public void setUpViewModel_cats(){
viewModel.getAllCats().observe(this, new Observer<List<CatsTable>>() {
#Override
public void onChanged(#Nullable List<CatsTable> catsTables) {
adapter.setCat_list((ArrayList<CatsTable>) catsTables);
}
});
}
// Add
public void insertDog() {
String dogName = String.valueOf(et_addDogName.getText()).trim();
DogsTable obj_dog = new DogsTable(dogName);
viewModel.insertDog(obj_dog);
Toast.makeText(this, "Dog Added", Toast.LENGTH_SHORT).show();
}
public void insertCat() {
String catName = String.valueOf(et_addCatName.getText());
CatsTable obj_cat = new CatsTable(catName);
viewModel.insertCat(obj_cat);
Toast.makeText(this, "cat Added", Toast.LENGTH_SHORT).show();
}
// Delete
public void swapToDelete_dog() {
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(final RecyclerView.ViewHolder viewHolder, int direction) {
List<DogsTable> dog_pos = adapter.getDog_list();
viewModel.deleteDog(dog_pos.get(viewHolder.getAdapterPosition()));
}
}
).attachToRecyclerView(recyclerView);
}
public void swapToDelete_cat() {
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int item_id = item.getItemId();
if (item_id == R.id.menu_add) {
Intent in = new Intent(this, Add.class);
startActivity(in);
}
return super.onOptionsItemSelected(item);
}
#Override
public void onItemClickListener(int pet_id) {
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="#+id/addDogNameET_xml"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:hint="add Dog name" />
<Button
android:id="#+id/AddDog_btn_xml"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="add" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="#+id/addCatNameET_xml"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:hint="add Cat name" />
<Button
android:id="#+id/AddCat_btn_xml"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="add" />
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView_id"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="MissingConstraints" />
</LinearLayout>
MainActivityAdapter.java
public class MainActivityAdapter extends RecyclerView.Adapter<MainActivityAdapter.MyViewHolder> {
Context mContext;
ArrayList<DogsTable> dog_list;
ArrayList<CatsTable> cat_list;
ItemClickListener mItemClickListener;
public MainActivityAdapter(Context context , ItemClickListener itemClickListener) {
this.mContext = context;
this.mItemClickListener = itemClickListener;
}
public interface ItemClickListener {
void onItemClickListener(int pet_id);
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(mContext).inflate(R.layout.activity_main_adapter, viewGroup, false);
return new MyViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull MyViewHolder holder, int position) {
DogsTable dog_pos = dog_list.get(position);
// CatsTable catsTable = cat_list.get(position);
holder.dogName.setText(String.valueOf(dog_pos.getDogName()));
// holder.catName.setText(String.valueOf(catsTable.getCatName()));
}
#Override
public int getItemCount() {
if (dog_list == null ) {
return 0;
} else {
return dog_list.size();
}
}
public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener , ItemClickListener {
TextView dogName;
TextView catName;
public MyViewHolder(#NonNull View itemView) {
super(itemView);
dogName = itemView.findViewById(R.id.dogName_xml);
catName = itemView.findViewById(R.id.catName_xml);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
int pet_id = dog_list.get(getAdapterPosition()).getDogs_id();
mItemClickListener.onItemClickListener(pet_id);
}
#Override
public void onItemClickListener(int pet_id) {
int pos = dog_list.get(getAdapterPosition()).getDogs_id();
mItemClickListener.onItemClickListener(pet_id);
}
}
public void setDog_list(ArrayList<DogsTable> dog_list) {
this.dog_list = dog_list;
notifyDataSetChanged();
}
public ArrayList<DogsTable> getDog_list() {
return dog_list;
}
public void setCat_list(ArrayList<CatsTable> cat_list) {
this.cat_list = cat_list;
}
}
activity_main_adapter.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"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Dogs: " />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/dogName_xml"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cats: " />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:id="#+id/catName_xml"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
To support different view types, RecyclerView.Adapter provides a useful method int getItemViewType(int position):
Return the view type of the item at position for the purposes of view recycling.
The default implementation of this method returns 0, making the assumption of a single view type for the adapter. Unlike ListView adapters, types need not be contiguous. Consider using id resources to uniquely identify item view types.
Then, in onCreateViewHolder you can see that a second parameter is int viewType which comes from the method int getItemViewType(int position). Based on that, you can instantiate a ViewHolder you need, e.g. DogViewHolder or CatViewHolder.
But what about storing multiple view models in a single adapter and defining which ViewHolder type should be actually instantiated? Here are two most popular approaches:
Declaring multiple containers for multiple types and defining a custom logic for getItemViewType method, e.g. all odd numbers will go in the dogs' list and even numbers will go in the cats' list (or any other method, but beware that you will have to cope with different lists' sizes and all the view types you need). Also, getItemsCount should be overriden appropriately (return list1.size() + list2.size + ... + listN.size();)
Put all the view models in a single list and perform some kind of attributes checks: either it will be some property or the type itself (not recommended for scalability reasons). Then your code will look like this:
public int getItemViewType(int position) {
CommonParentForUpcasting item = items.get(position);
if (item instanceOf Dog) { // or something like item.type == Animal.CAT
return R.id.holder_dog;
} else {
return R.id.holder_cat;
}
}
If you want to come up with a second solution, this solution should suit you well.
Also, make sure to check this StackOverflow answer.
I am trying to listen for row clicks (item clicks) on my recycler view from the Activity itself (not from adapter).
My Adapter so far looks like this:
public class ListMiestnostiAdapter extends RecyclerView.Adapter<ListMiestnostiAdapter.ViewHolder>{
private List<Miestnost> data;
// you provide access to all the views for a data item in a view holder
public static class ViewHolder extends RecyclerView.ViewHolder {
public ViewDataBinding binding;
public ViewHolder(ViewDataBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(Object obj){
binding.setVariable(BR.obj, obj);
binding.executePendingBindings();
}
}
public ListMiestnostiAdapter(List<Miestnost> data) {
this.data = data;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// create a new view
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
ActivityListMiestnostiRowBinding itemBinding = ActivityListMiestnostiRowBinding.inflate(inflater, parent,false);
return new ViewHolder(itemBinding);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Miestnost item = data.get(position);
holder.bind(item);
}
#Override
public int getItemCount() {
return data.size();
}
}
And my row layout like this:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="obj"
type="com.example.com.projectname.Models.Miestnost" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:paddingBottom="#dimen/row_padding_vertical"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/row_padding_vertical">
<TextView
android:id="#+id/txt_miestnost_row_nazov"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:text="#{obj.Name}"
android:textSize="16dp" />
</LinearLayout>
</layout>
As I have already mentioned I am trying to listen to item clicks, but not from Adapter itself (as other posts suggested), but from my Activity which holds this RecyclerView.
Is such action even possible? If yes, then any help would be highly appreciated as I could not find anything on google.
You’ll first need an interface that specifies listener’s behavior. In this example, there is a sample model called ContentItem, so the click will return an item of that type:
public interface OnItemClickListener {
void onItemClick(ContentItem item);
}
The constructor will receive an object that implements this interface, along with the items to be rendered:
private final List<ContentItem> items;
private final OnItemClickListener listener;
public ContentAdapter(List<ContentItem> items, OnItemClickListener listener) {
this.items = items;
this.listener = listener;
}
You could alternatively create a setOnItemClickListener method and assign it that way. Now, in onBindViewHolder the ViewHolder will receive the constructor in the custom bind method:
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.bind(items.get(position), listener);
}
This is how this bind method looks:
public void bind(final ContentItem item, final OnItemClickListener listener) {
...
itemView.setOnClickListener(new View.OnClickListener() {
#Override public void onClick(View v) {
listener.onItemClick(item);
}
});
}
Use it whenever you need it by creating a new adapter and the listener that will implement the behavior when an item is clicked. A simple example:
recycler.setAdapter(new ContentAdapter(
items,
new ContentAdapter.OnItemClickListener() {
#Override
public void onItemClick(ContentItem item) {
Toast.makeText(getContext(), "Item Clicked", Toast.LENGTH_LONG).show();
}
}));
Source
To add to the answer provided by João Alvares Neto, we can call the OnItemClickListener directly from the layout.
In the variable tag, we can pass the OnItemClickListener and call it using the android:onClick property.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="storyClickListener"
type="com.hoomanwe.zorro.ui.adapter.OnStoryClickListener" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{() -> storyClickListener.onStoryClick(item.id)}" />
</layout>
By this way, you can eliminate the following code from the solution provided by João Alvares Neto
public void bind(final ContentItem item, final OnItemClickListener listener) {
itemView.setOnClickListener(new View.OnClickListener() {
#Override public void onClick(View v) {
listener.onItemClick(item);
}
});
}
I am Referring to vogella-tutorial for databinding
What i am trying to do: What is the best way to detect onClick in recycler view row for each Item using the dataBinding
activity_second.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="temp"
type="com.vogella.android.databinding.TemperatureData" />
<variable
name="presenter"
type="com.vogella.android.databinding.MainActivityPresenter"/>
</data>
<android.support.v7.widget.RecyclerView
android:id="#+id/my_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
</layout>
rowlayout.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="obj"
type="com.vogella.android.databinding.TemperatureData"
/>
</data>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:padding="6dip"
>
<ImageView
android:id="#+id/icon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
android:layout_marginRight="6dip"
android:contentDescription="TODO"
android:src="#drawable/ic_listentry"
/>
<TextView
android:id="#+id/secondLine"
android:layout_width="fill_parent"
android:layout_height="26dip"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_toRightOf="#id/icon"
android:ellipsize="marquee"
android:text="#{obj.location}"
android:textSize="12sp"
android:maxLines="1"
/>
<TextView
android:id="#+id/firstLine"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="#id/secondLine"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_alignWithParentIfMissing="true"
android:layout_toRightOf="#id/icon"
android:gravity="center_vertical"
android:text="#{obj.celsius}"
android:textSize="16sp"
/>
</RelativeLayout>
</layout>
MyAdapter.java
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private List<TemperatureData> data;
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public class MyViewHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
private final ViewDataBinding binding;
public MyViewHolder(ViewDataBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(Object obj) {
binding.setVariable(BR.obj,obj);
binding.executePendingBindings();
}
}
// Provide a suitable constructor (depends on the kind of dataset)
public MyAdapter(List<TemperatureData> myDataset) {
data = myDataset;
}
// Create new views (invoked by the layout manager)
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// create a new view
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
ViewDataBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.rowlayout, parent, false);
// set the view's size, margins, paddings and layout parameters
return new MyViewHolder(binding);
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
final TemperatureData temperatureData = data.get(position);
holder.bind(temperatureData);
}
// Return the size of your dataset (invoked by the layout manager)
#Override
public int getItemCount() {
return data.size();
}
}
MyAdapter.java
public class MyAdapter extends MyBaseAdapter {
List<TemperatureData> data;
// Provide a suitable constructor (depends on the kind of dataset)
public MyAdapter(List<TemperatureData> myDataset) {
data = myDataset;
}
#Override
public Object getDataAtPosition(int position) {
return data.get(position);
}
#Override
public int getLayoutIdForType(int viewType) {
return R.layout.rowlayout;
}
#Override
public int getItemCount() {
return data.size();
}
}
Not sure if you have already found a solution, but I managed to do it quite easily.
1) modify onCreateViewHolder method to look like this:
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// create a new view
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
ViewDataBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.rowlayout, parent, false);
MainActivityPresenter presenter = new MainActivityPresenter(this, parent.getContext());
binding.setVariable(BR.presenter,presenter);
// set the view's size, margins, paddings and layout parameters
return new MyViewHolder(binding);
}
2) make MyAdapter to implement MainActivityContract.View so in the end it looks like following:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> implements MainActivityContract.View
3) Implement necessary methods within MyAdapter; e.g:
#Override
public void showData(TemperatureData data) {
String clickedItemCelsius = data.getCelsius();
}
4) Add Presenter variable to your row layout file:
<variable
name="presenter"
type="com.mvvm.ViewModels.MainActivityPresenter"/>
5) Finally hook your onClick event under RelativeLayout:
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:padding="6dip"
android:onClick="#{() -> presenter.onShowData(obj)}"
>
Hope it helps!
our viewModel used in recycler view
class UserViewModel (val name: String?, val onClick: () -> Unit)
layout for user_item.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="model"
type="...model.UserViewModel" />
</data>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:onClick="#{()->model.onClick.invoke()}"
android:text="#{model.name}" />
<merge>
creating of models in presenter or modelView or somewhere else
fun loadData() {
// ..
val user = UserViewModel("name") { handleUserEvent() }
.. //
}
fun handleUserEvent() {
// TODO handle on click
}
Probably the most common solution would be to put a click listener on the row layout's root view and call a method on your view model.
For example in rowlayout.xml:
...
<RelativeLayout
android:onClick="#{() -> obj.performClickAction()}"
....
Hey I read that article about a week ago and had the same problem! The article barely mentions how actions should be handled but there is documentation on how to do it. In short, you are going to want a handler.
This handler is defined in your xml
<data>
...
<variable name="handlers" type="com.example.MyHandlers"/>
...
</data>
example usage
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.firstName}"
android:onClick="#{handlers::onClickFriend}"/>
The MyHandlers.java would look like this
public class MyHandlers {
public void onClickFriend(View view) { ... }
}
You would change the add one more line to your MyAdapter.java
public class MyViewHolder extends RecyclerView.ViewHolder {
public void bind(Object obj) {
binding.setVariable(BR.obj,obj);
binding.executePendingBindings();
binding.setHandlers(new MyHandlers());
}
I haven't tested this code but if this doesn't work I can share my adapter .
If other suggestions don't seem to work the way you think it should, take a look at Google Codelab training, specifically "Interacting with RecyclerView items". It's a part of series, but if you are interested in handling the click event in RecyclerView (with Data Binding), you just need to read the aforementioned chapter only.
In short, 1) create a listener class with onClick(), 2) add listener class as data in list item's xml layout file, and 3) use android:onClick="{...}" to map the list item's click event to the listener.
I'm sure there are other ways to achieve the same goal, but this approach seems fairly straightforward.
This way we can use the item click on databinding
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.CustomView> {
List<NewsModel> newsList;
private LayoutInflater layoutInflater;
public CustomAdapter(List<NewsModel> newsList)
{
this.newsList = newsList;
}
#Override
public CustomView onCreateViewHolder(final ViewGroup parent, final int viewType) {
if(layoutInflater == null)
{
layoutInflater = LayoutInflater.from(parent.getContext());
}
final NewsBinding newsBinding = NewsBinding.inflate(layoutInflater,parent,false);
newsBinding.setPresenter(new ClickListener() {
#Override
public void onclickListener() {
Log.d("click me ","click me "+newsBinding.getNewsview().Title);
Toast.makeText(parent.getContext(),""+newsBinding.getNewsview().Title,Toast.LENGTH_LONG).show();
}
});
// View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.innerlayout,parent,false);
return new CustomView(newsBinding);
}
#Override
public void onBindViewHolder(CustomView holder, int position) {
// News news = newsList.get(position);
// holder.title.setText(news.getTitle());
// holder.desc.setText(news.getDesc());
NewsModel newsModel = newsList.get(position);
holder.bind(newsModel);
}
#Override
public int getItemCount() {
return newsList.size();
}
public class CustomView extends RecyclerView.ViewHolder {
private NewsBinding newsBinding;
// TextView title, desc;
public CustomView(NewsBinding newsBinding) {
super(newsBinding.getRoot());
this.newsBinding = newsBinding;
//title = (TextView)itemView.findViewById(R.id.titleval);
//desc =(TextView)itemView.findViewById(R.id.descval);
}
public void bind(NewsModel newsModel)
{
this.newsBinding.setNewsview(newsModel);
}
public NewsBinding getNewsBinding()
{
return newsBinding;
}
}
}
complete project is https://github.com/Vishulucky/DataBinding-MVVM.git
class MyAdapter() :
RecyclerView.Adapter<MyAdapter.MyViewHolder> {
inner class MyViewHolder(private val binding: ItemMyListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(yourItem: YourItem) {
binding.setVariable(BR.item, yourItem)
binding.executePendingBindings()
binding.animeListCard.setOnClickListener {
onItemClickListener?.let { click ->
click(yourItem)
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding: ItemMyListBinding = DataBindingUtil.inflate(
layoutInflater,
R.layout.item_My_list,
parent,
false
)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(getItem(position))
}
//add these
private var onItemClickListener: (( YourItem) -> Unit)? = null
fun setOnItemClickListener(listener: ( YourItem) -> Unit) {
onItemClickListener = listener
}
}
R.layout.item_My_list layout:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="item"
type="com.temp.example.data.MyItem" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:orientation="vertical">
<TextView
android:id="#+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{item.title}"
/>
</LinearLayout>
</layout>
then in your activity :
myAdapter.setOnItemClickListener { item->
//your code here
}