Как отправить событие нажатия на кнопку внутри элемента в recylcerview при соблюдении архитектуры MVVM? - PullRequest
1 голос
/ 13 апреля 2020

У меня проблема, она техническая и концептуальная, я пытаюсь использовать архитектуру MVVM и не хочу от нее отказываться, поэтому у меня есть список элементов, у каждого элемента есть кнопка удаления, я показать список с помощью recylcerview, поэтому я использую адаптер, теперь, когда я вызываю событие в каждой строке, я делаю это:

Это мой адаптер, go для deleteBtn внутри 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);
            }
        }
    }
}

Это моя 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();
    }
}

Поэтому, когда я вызываю действие и наблюдаю его, я обычно делаю это в своей MainActivity, потому что я выполнял создание экземпляра ListViewModel внутри MainActivity, и вызывал мои методы ViewModel, но теперь я чувствую, что мне нужно создать те же ViewModels в этом ViewHolder.

Сначала я не знаю как, потому что когда я вызываю «this» в моей MainActivity, это относится к MainActvity, но если я вызываю его в ViewHolder (не активность), он не будет работать, так как я буду вызывать мою ViewModel в ViewHolder.

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

Во-вторых, и это более важно, уважаю ли я в архитектуре MVVM, если я это сделаю?

Потому что, как я это представляю:

    @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

        }
    }

Любая помощь будет высоко ценится, ребята!

Спасибо.

Ответы [ 3 ]

2 голосов
/ 13 апреля 2020

Это ваша обновленная активность.

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

Это ваша обновленная 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);
    }
}
1 голос
/ 13 апреля 2020

Я бы создал функцию обратного вызова в Activity и позволил бы Activity реализовать ее:

interface OnDeleteClicklistener {
     void onDelete(int id) 
}

Тогда действие вызвало бы свой ViewModel внутри этого метода:

void onDelete(int id) {
     viewModel.deleteItem(id)
}

Тогда вместо передачи ViewModel для Адаптера, я бы передал действие в качестве интерфейса:

recyclerView.setAdapter(MyAdapter(this, listOfItems)

Конструктор адаптера:

MyAdapter(OnDeleteClicklistener listener, List<Item> list) 
1 голос
/ 13 апреля 2020

Вы должны использовать адаптер привязки для нажатий кнопок в архитектуре MVVM с привязкой данных. Все нажатия кнопок будут в одном классе.

<android.support.design.widget.Button
            ...
            android:onClick="@{handlers::ButtonClicked}" />

public class MyClickHandlers {

        public void onButtonClicked(View view) {
            Toast.makeText(getApplicationContext(), "button clicked!", Toast.LENGTH_SHORT).show();
        }
}
...