Search code examples
androidmvvmandroid-recyclerviewandroid-arrayadapterviewmodel

How to submit a click event on a button inside an item in a recylcerview while respecting MVVM architecture?


I am having a problem, it is technical and conceptual, I am trying to use MVVM architecture and I don't want to drive away from it, so I have a list of items, each item has a delete button, I show the list using recylcerview, so I am using an adapter, now when I call an event on each row I do this :

This is my Adapter, go to deleteBtn inside the ViewHolder.

    public class ContractListAdapter extends RecyclerView.Adapter<ContractListAdapter.ContractViewHolder> {

    List<ContractModel> contracts;
    Context context;
    public ContractListAdapter(Context context, List<ContractModel> contracts){
        this.context = context;
        this.contracts = contracts;
    }

    @NonNull
    @Override
    public ContractViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ContratBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.contrat, parent, false); // ContratBinding >> as your list item layout named "contrat"
        return new ContractViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(@NonNull ContractViewHolder holder, int position) {
        holder.binding.setContract(contracts.get(position));
    }

    @Override
    public int getItemCount() {
        return this.contracts.size();
    }

    public class ContractViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{

        ContractsListViewModel listViewModel;

        @BindView(R.id.courtier)
        TextView courtier;

        @BindView(R.id.delete)
        Button deleteButton;

        ContratBinding binding;

        @BindView(R.id.contratImage)
        ImageView contractImage;

        public ContractViewHolder(@NonNull ContratBinding binding){
            super(binding.getRoot());
            this.binding = binding;
            ButterKnife.bind(this, itemView);

            itemView.setOnClickListener(this);

            courtier.setOnClickListener(this);
            deleteButton.setOnClickListener(this);
            contractImage.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {

            int position = getAdapterPosition();
            int idOfContract = contracts.get(position).getId();

            if(deleteButton.getId() == v.getId()){
                // action here
            }
            else {
                Intent myIntent = new Intent(context, ContractActivity.class);
                myIntent.putExtra("key", idOfContract + ""); //Optional parameters
                context.startActivity(myIntent);
            }
        }
    }
}

This is my MainActivity :

public class MainActivity extends AppCompatActivity {

    ContractsListViewModel contractsListViewModel;

    @BindView(R.id.contractList)
    RecyclerView contractList;

    @BindView(R.id.newContractBtn)
    Button newContractBtn;

    @BindView(R.id.listLoading)
    ProgressBar listLoading;

    @BindView(R.id.listError)
    TextView listError;


    RecyclerView.Adapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        ActivityMainBinding main = DataBindingUtil.setContentView(this, R.layout.activity_main);

        ButterKnife.bind(this);

        main.contractList.setLayoutManager(new LinearLayoutManager(this));

        contractsListViewModel = ViewModelProviders.of(this).get(ContractsListViewModel.class);
        contractsListViewModel.call();

        contractsListViewModel.contractList.observe(this,contractModels -> {
            if(contractModels != null){

                contractList.setVisibility((View.VISIBLE));
                adapter = new ContractListAdapter(MainActivity.this, contractModels);
                main.contractList.setAdapter(adapter);
            }
        });

        contractsListViewModel.isLoading.observe(this,isLoading -> {
            if(isLoading != null){
                listLoading.setVisibility(isLoading ? View.VISIBLE : View.GONE );
                if(isLoading){
                    listError.setVisibility(View.GONE);
                    contractList.setVisibility((View.GONE));
                }
            }
        });

        contractsListViewModel.error.observe(this,Error -> {
            if(Error != null){
                listError.setVisibility(Error ? View.VISIBLE : View.GONE );
            }
        });

        newContractBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent newContractIntent = new Intent(getApplicationContext(), NewContractActivity.class);
                MainActivity.this.startActivity(newContractIntent);
            }
        });

    }

    @Override
    public void onResume()
    {
        super.onResume();
        contractsListViewModel = ViewModelProviders.of(this).get(ContractsListViewModel.class);
        contractsListViewModel.call();
    }
}

So when I call an action and observe it I usually do in my MainActivity because I was instanciating my ListViewModel inside the MainActivity, and calling my ViewModel methods, but now I feel like I have to instantiate the same ViewModels in this ViewHolder.

First I don't know how, because when I call 'this' in my MainActivity it refers to MainActvity but If I call it in the ViewHolder ( not an activity ), it won't works, so how will I call my ViewModel in the ViewHolder.

contractsListViewModel = ViewModelProviders.of(this).get(ContractsListViewModel.class);

Second and it is more important, Am I respecting the MVVM architecture if I do this ?

Because how I am conceptualizing it :

    @Override
    public void onClick(View v) {

        int position = getAdapterPosition();
        int idOfContract = contracts.get(position).getId();

        // Delete Button clicked
        if(deleteButton.getId() == v.getId()){

           // I call the ViewModel

           // call the action I want ( delete request )

           // observe it in my MainActivity

        }
    }

Any help would be much appreciated guys!

Thank you.


Solution

  • This is your updated Activity.

    public class MainActivity extends AppCompatActivity {
    
        ContractsListViewModel contractsListViewModel;
    
        @BindView(R.id.contractList)
        RecyclerView contractList;
    
        @BindView(R.id.newContractBtn)
        Button newContractBtn;
    
        @BindView(R.id.listLoading)
        ProgressBar listLoading;
    
        @BindView(R.id.listError)
        TextView listError;
    
    
        RecyclerView.Adapter adapter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
    
            super.onCreate(savedInstanceState);
            ActivityMainBinding main = DataBindingUtil.setContentView(this, R.layout.activity_main);
    
            ButterKnife.bind(this);
    
            main.contractList.setLayoutManager(new LinearLayoutManager(this));
    
            contractsListViewModel = ViewModelProviders.of(this).get(ContractsListViewModel.class);
            contractsListViewModel.call();
    
            contractsListViewModel.contractList.observe(this,contractModels -> {
                if(contractModels != null){
                    contracts.addAll(contractModels);
    
                    contractList.setVisibility((View.VISIBLE));
                    adapter = new ContractListAdapter(MainActivity.this, contractModels, new ContractListAdapter.OnButtonPressed() {
                        @Override
                        public void onClicked(int position) {
                            contractsListViewModel.callDelete(position);
                        }
                    });
                    main.contractList.setAdapter(adapter);
                }
            });
    
            contractsListViewModel.isLoading.observe(this,isLoading -> {
                if(isLoading != null){
                    listLoading.setVisibility(isLoading ? View.VISIBLE : View.GONE );
                    if(isLoading){
                        listError.setVisibility(View.GONE);
                        contractList.setVisibility((View.GONE));
                    }
                }
            });
    
            contractsListViewModel.error.observe(this,Error -> {
                if(Error != null){
                    listError.setVisibility(Error ? View.VISIBLE : View.GONE );
                }
            });
    
            newContractBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent newContractIntent = new Intent(getApplicationContext(), NewContractActivity.class);
                    MainActivity.this.startActivity(newContractIntent);
                }
            });
    
        }
    
        @Override
        public void onResume()
        {
            super.onResume();
            contractsListViewModel = ViewModelProviders.of(this).get(ContractsListViewModel.class);
            contractsListViewModel.call();
        }
    }
    

    This is your updated ContractListAdapter

    public class ContractListAdapter extends RecyclerView.Adapter<ContractListAdapter.ContractViewHolder> {
    
        List<ContractModel> contracts;
        Context context;
        OnButtonPressed onButtonPressed;
    
        public ContractListAdapter(Context context, List<ContractModel> contracts,OnButtonPressed onButtonPressed) {
            this.context = context;
            this.contracts = contracts;
            this.onButtonPressed = onButtonPressed;
        }
    
        @NonNull
        @Override
        public ContractViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            ContratBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.contrat, parent, false); // ContratBinding >> as your list item layout named "contrat"
            return new ContractViewHolder(binding);
        }
    
        @Override
        public void onBindViewHolder(@NonNull ContractViewHolder holder, int position) {
            holder.binding.setContract(contracts.get(position));
        }
    
        @Override
        public int getItemCount() {
            return this.contracts.size();
        }
    
        public class ContractViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    
            ContractsListViewModel listViewModel;
    
            @BindView(R.id.courtier)
            TextView courtier;
    
            @BindView(R.id.delete)
            Button deleteButton;
    
            ContratBinding binding;
    
            @BindView(R.id.contratImage)
            ImageView contractImage;
    
            public ContractViewHolder(@NonNull ContratBinding binding) {
                super(binding.getRoot());
                this.binding = binding;
                ButterKnife.bind(this, itemView);
    
                itemView.setOnClickListener(this);
    
                courtier.setOnClickListener(this);
                deleteButton.setOnClickListener(this);
                contractImage.setOnClickListener(this);
            }
    
            @Override
            public void onClick(View v) {
    
                int position = getAdapterPosition();
                int idOfContract = contracts.get(position).getId();
    
                if (deleteButton.getId() == v.getId()) {
                    // action here
                    onButtonPressed.onClicked(position);
                } else {
                    Intent myIntent = new Intent(context, ContractActivity.class);
                    myIntent.putExtra("key", idOfContract + ""); //Optional parameters
                    context.startActivity(myIntent);
                }
            }
        }
    
        interface OnButtonPressed {
            void onClicked(int position);
        }
    }