I'm new to Android and I'm trying to implement a Barcode reader scenario with the new architecture components.
Every time a barcode is read, I want to update a list in the ViewModel adding a new element if the barcode is not present on the list or increasing the quantity otherwise.
The following solution is working, but it's not satisfying me about calling "notifyDataSetChanged" on the adapter in order to update UI. That's because ViewModel list and adapter internal list contain references to same objects so DiffUtil won't catch any changes.
Is there a better way to update UI? Beside adapter, are there any improvement I should consider to handle architecture components?
ViewModel
public class ScannerViewModel extends ViewModel {
private MutableLiveData<List<ProductScan>> scanListLD;
public ScannerViewModel() {
scanListLD = new MutableLiveData<>();
scanListLD.setValue(new ArrayList<ProductScan>());
}
public LiveData<List<ProductScan>> getScanList() {
return scanListLD;
}
public void addBarcode(String barcode) {
List<ProductScan> list = scanListLD.getValue();
ProductScan scan = null;
for (ProductScan item : list) {
if (item.barcode.equals(barcode)) {
scan = item;
break;
}
}
if (scan == null) {
scan = new ProductScan();
scan.barcode = barcode;
scan.qt = 0;
list.add(scan);
}
scan.qt += 1;
scanListLD.postValue(list);
}
}
Adapter
public class ScannerAdapter extends RecyclerView.Adapter<ScannerAdapter.ViewHolder> {
private List<ProductScan> scans;
public interface OnItemClickListener {
void onItemClick();
}
public ScannerAdapter(List<ProductScan> scans) {
this.scans = scans;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.template_scan, parent, false);
ScannerAdapter.ViewHolder viewHolder = new ScannerAdapter.ViewHolder(view);
return viewHolder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.bindData(scans.get(position), position);
}
@Override
public int getItemCount() {
return scans != null ? scans.size() : 0;
}
//Not used since scans and newScans contain same objects
public void setScans(final List<ProductScan> newScans) {
if (scans == null) {
scans = new ArrayList<ProductScan>();
scans.addAll(newScans);
notifyItemRangeInserted(0, newScans.size());
} else {
DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
@Override
public int getOldListSize() {
return scans != null ? scans.size() : 0;
}
@Override
public int getNewListSize() {
return newScans != null ? newScans.size() : 0;
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
ProductScan oldScan = scans.get(oldItemPosition);
ProductScan newScan = newScans.get(newItemPosition);
return Utils.equals(oldScan.barcode,newScan.barcode);
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
ProductScan oldScan = scans.get(oldItemPosition);
ProductScan newScan = newScans.get(newItemPosition);
return Utils.equals(newScan.barcode, oldScan.barcode)
&& Utils.equals(newScan.productId, oldScan.productId)
&& Utils.equals(newScan.productDescription, oldScan.productDescription)
&& Utils.equals(newScan.qt, oldScan.qt);
}
});
scans.clear();
scans.addAll(newScans);
result.dispatchUpdatesTo(this);
}
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TemplateScanBinding binding;
public ViewHolder(View itemView) {
super(itemView);
binding = DataBindingUtil.bind(itemView);
}
public void bindData(ProductScan scan, int position) {
binding.setScan(scan);
binding.setBtnIncreaseQtListener(() -> {
scan.qt += 1;
notifyItemChanged(position);
});
binding.setBtnDecreaseQtListener(() -> {
scan.qt = Math.max(1, scan.qt - 1);
notifyItemChanged(position);
});
binding.edtQt.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) {
if (i == EditorInfo.IME_ACTION_DONE) {
binding.edtQt.clearFocus();
}
return false;
}
});
binding.edtQt.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean b) {
if (scan.qt == null || scan.qt < 1) {
scan.qt = 1;
notifyItemChanged(position);
}
InputMethodManager imm = (InputMethodManager) binding.getRoot().getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
});
}
}
}
Activity
public class ScannerActivity extends ScannerBaseActivity {
@Inject
ViewModelFactory viewModelFactory;
private ScannerViewModel viewModel;
private ActivityScannerBinding binding;
private ScannerAdapter adapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((MyApplication) getApplication()).getComponent().inject(this);
viewModel = ViewModelProviders.of(this, viewModelFactory).get(ScannerViewModel.class);
binding = DataBindingUtil.setContentView(this, R.layout.activity_scanner);
adapter = new ScannerAdapter(viewModel.getScanList().getValue());
binding.recvScans.setLayoutManager(new LinearLayoutManager(this));
binding.recvScans.setAdapter(adapter);
viewModel.getScanList().observe(this, (list) -> {
adapter.notifyDataSetChanged();
});
binding.btnScan.setOnClickListener((v) -> {
//calls viewModel.addBarcode(String barcode)
readBarcode(readBarcodeCallback);
});
}
}