Reputation: 1315
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.
Upvotes: 0
Views: 211
Reputation: 791
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);
}
}
Upvotes: 1
Reputation: 616
I would create a callback function in Activity and let Activity implement it:
interface OnDeleteClicklistener {
void onDelete(int id)
}
Then the activity would call its ViewModel inside this method:
void onDelete(int id) {
viewModel.deleteItem(id)
}
Then instead of passing the ViewModel to the Adapter, I would pass the activity as the interface:
recyclerView.setAdapter(MyAdapter(this, listOfItems)
Adapter constructor:
MyAdapter(OnDeleteClicklistener listener, List<Item> list)
Upvotes: 1
Reputation: 358
You have to use binding adapter for your button clicks in MVVM architecture with data binding.All your button clicks will be in the same class.
<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();
}
}
Upvotes: 1