Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

livedata list - adapter doesn't update with DiffUtil method

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

    }
}
like image 709
user2692281 Avatar asked Nov 18 '22 06:11

user2692281


1 Answers

You can use PagedListAdapterHelper in your existing adapter as shown below

class UserAdapter extends RecyclerView.Adapter<UserViewHolder> {
     private final PagedListAdapterHelper<User> mHelper;
     public UserAdapter(PagedListAdapterHelper.Builder<User> builder) {
         mHelper = new PagedListAdapterHelper(this, DIFF_CALLBACK);
     }
     @Override
     public int getItemCount() {
         return mHelper.getItemCount();
     }
     public void setList(PagedList<User> pagedList) {
         mHelper.setList(pagedList);
     }
     @Override
     public void onBindViewHolder(UserViewHolder holder, int position) {
         User user = mHelper.getItem(position);
         if (user != null) {
             holder.bindTo(user);
         } else {
             // Null defines a placeholder item - PagedListAdapterHelper will automatically
             // invalidate this row when the actual object is loaded from the database
             holder.clear();
         }
     }
     public static final DiffCallback<User> DIFF_CALLBACK = new DiffCallback<User>() {
          @Override
          public boolean areItemsTheSame(
                  @NonNull User oldUser, @NonNull User newUser) {
              // User properties may have changed if reloaded from the DB, but ID is fixed
              return oldUser.getId() == newUser.getId();
          }
          @Override
          public boolean areContentsTheSame(
                  @NonNull User oldUser, @NonNull User newUser) {
              // NOTE: if you use equals, your object must properly override Object#equals()
              // Incorrectly returning false here will result in too many animations.
              return oldUser.equals(newUser);
          }
      }

OR

User PageListAdapter

class UserAdapter extends PagedListAdapter<User, UserViewHolder> {
     public UserAdapter() {
         super(DIFF_CALLBACK);
     }
     @Override
     public void onBindViewHolder(UserViewHolder holder, int position) {
         User user = getItem(position);
         if (user != null) {
             holder.bindTo(user);
         } else {
             // Null defines a placeholder item - PagedListAdapter will automatically invalidate
             // this row when the actual object is loaded from the database
             holder.clear();
         }
     }
     public static final DiffCallback<User> DIFF_CALLBACK = new DiffCallback<User>() {
         @Override
         public boolean areItemsTheSame(
                 @NonNull User oldUser, @NonNull User newUser) {
             // User properties may have changed if reloaded from the DB, but ID is fixed
             return oldUser.getId() == newUser.getId();
         }
         @Override
         public boolean areContentsTheSame(
                 @NonNull User oldUser, @NonNull User newUser) {
             // NOTE: if you use equals, your object must properly override Object#equals()
             // Incorrectly returning false here will result in too many animations.
             return oldUser.equals(newUser);
         }
     }
like image 96
Prashant Kawtikwar Avatar answered Dec 04 '22 14:12

Prashant Kawtikwar