user9897182
user9897182

Reputation: 265

Databinding Recyclerview and onClick

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);

Upvotes: 1

Views: 7134

Answers (3)

Danail Alexiev
Danail Alexiev

Reputation: 7772

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.

Upvotes: 3

VIVEK CHOUDHARY
VIVEK CHOUDHARY

Reputation: 546

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.

Upvotes: 0

Notsileous
Notsileous

Reputation: 560

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.

Upvotes: 0

Related Questions