DiffUtil not calling onBindView or refreshing ViewHolder - java

I tried to use DiffUtil approach to update my list which always consist of 30 items, now each item data updates every minute but no way of telling if all item's data will have updates so to avoid abusing notifyDataSetChanged() I create a class extending DiffUtil.
public class DifUtil extends DiffUtil.Callback {
private final List<Asset> oldList, newList;
public DifUtil(List<Asset> newList, List<Asset> oldList) {
this.oldList = oldList;
this.newList = newList;
}
#Override
public int getOldListSize() {
return oldList.size();
}
#Override
public int getNewListSize() {
return newList.size();
}
#Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldList.get(oldItemPosition).getId().equals(newList.get(newItemPosition).getId());
}
#Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return oldList.get(oldItemPosition).equals(newList.get(newItemPosition));
}
#Nullable
#Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
//you can return particular field for changed item.
return super.getChangePayload(oldItemPosition, newItemPosition);
}
}
Add new public function to notify the adapter with this
public void updateList(List<Asset> newList) {
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DifUtil(newList, this.assetList));
this.assetList.clear();
this.assetList.addAll(newList);
diffResult.dispatchUpdatesTo(this);
}
Overriding another onBindViewHolder (not sure if needed when not using payload)
onBindViewHolder(#NonNull AssetsAdapter.ViewHolder holder, int position, #NonNull List<Object> payloads)
Then updating the list by just calling
adapter.updateList(newAssetList);
The updating of list works but I can only see those new values by scrolling the list, I need to view the updates even without recycling the view (when scrolling) just like notifyItemChanged().
To my understanding calling dispatchUpdatesTo should handle and update the views and its data or am I missing something here please enlighten me.

I managed to make it work by following this blog and its way of implementing DiffUtil.
https://www.journaldev.com/20873/android-recyclerview-diffutil
However I did not perform the cloning part since it is irrelevant on my case.
BONUS
There is a known bug where updates makes the list to scroll at the bottom. I get rid of this by saving scroll state with LayoutManager like in this SO answer.
https://stackoverflow.com/a/44053550/11338467
So my adapter DiffUtil update part will be this
public void updateList(List<Asset> newList) {
Parcelable recyclerViewState = layoutManager.onSaveInstanceState();
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DifUtil(newList, this.assetList));
diffResult.dispatchUpdatesTo(this);
this.assetList.clear();
this.assetList.addAll(newList);
layoutManager.onRestoreInstanceState(recyclerViewState);
}

Related

Transformations.distinctUntilChanged not working as intended?

I have multiple instances of same viewmodel in different fragments(scoped to fragments). Whenever I update LiveData in Room, it triggers observable in fragment but it doesn't trigger one instance that should but all of instances(even though the values in others are same). I have thought of a solution as Transformations.distinctUntilChanged but it is not working. My code:
#Dao
public interface PlayerDao {
#Query("SELECT * from player_table WHERE id_playera=:id")
LiveData<PlayerEntity> getPlayer(final int id);
}
public class PlayerRepository {
public LiveData<PlayerEntity> getPlayer(final int id) {
return playerDao.getPlayer(id);
}
}
public class PlayerViewModel extends AndroidViewModel {
public LiveData<PlayerEntity> getPlayer(final int id) {
return Transformations.distinctUntilChanged(repository.getPlayer(id));
}
}
#Override
public void onViewCreated(#NonNull final View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
playerViewModel = new ViewModelProvider(this).get(PlayerViewModel.class);
playerViewModel.getPlayer(redniBrojPlayera).observe(getViewLifecycleOwner(),
newObserver<PlayerEntity>() {
#Override
public void onChanged(PlayerEntity playerEntity) {
//triggers UI
}
});
}
I tried fetching Livedata-Integer from Room and then applying Transformations.distinctUntilChanged() in viewmodel and it worked as intended only triggering one that changed. So I am wondering is it even possible to do this and why are observables triggering even through transformations.
The problem was that I didn't Override equals method in my Entity.class. Therefore it would return completely new object and Transformation.distinctUntilChanged would trigger.

Correctly updating Jlist by calling firedatachanged

So i need to update the jList dynamically. I read that i have to use a fireData.... or something similar depending on the api.
I'm using AbstractListModel for the jList in the following code.
/*private javax.swing.JList<String> anycast_users;*/
void set_user_model(){
anycast_users.setModel(new javax.swing.AbstractListModel<String>() {
#Override
public int getSize() {
return anycast_users_num;
}
#Override
public String getElementAt(int i) {
return anycast_users_list[i];
}
});
/*listeners and other init..*/
The AbstractListNodel has this function
protected void fireContentsChanged(Object source,
int index0,
int index1)
So, to update the list i initially tried
anycast_users.getModel(). //in a different function (updateList())
But as fireContentsChanged is protected i can't call it from here.
So, how to call the function correctly, and what additional changes is required?
I would create a method in that class where you have access to the model and make something like this:
public ListModel<String> getAnycastUsersModel() {
return this.anycast_users.getModel();
}
This way you can access it from wherever you want to.
And I would suggest you using DefaultListModel if you're able to :)
I'm going with a solution suggested by MadProgrammer.
class anycastModel extends javax.swing.AbstractListModel<String>{
#Override
public int getSize() {
return anycast_users_num;
}
#Override
public String getElementAt(int i) {
return anycast_users_list[i];
}
public void updateList(){
fireContentsChanged(this, 0, anycast_users_num);
}
}
void set_usermodel(){
anycast_model=new anycastModel();
anycast_users.setModel(anycast_model);
}
For updating
anycast_model.updateList();

jList not displaying data in specific case

In my program, I have a jList and I can add, delete, modify items in this Jlist.
My problem is, if I click on my add button before selecting an item in my jList, the items inside the jList disapear. (only in apeareance because they are actually still in the jList)
If, before that, I select an item in my list, then everything is working fine. So my guess would be that the "valueChanged()" method from my listener is doing something that I don't do myself.
Here is my list initialisation, which I call at the start of the program:
public final void initList() {
jListPaiement.setModel(new MyListModel(ls.getDb().getListePaiements()));
final DecimalFormat df = new DecimalFormat("###.##");
jListPaiement.addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent lse) {
MoyenPaiement mp = (MoyenPaiement) ((MyListModel) jListPaiement.getModel()).getElementAt(jListPaiement.getSelectedIndex());
jTextFieldFF.setText(df.format(mp.getFraisf()));
jTextFieldFV.setText(df.format(mp.getFraisv() * 100));
jTextFieldNomP.setText(mp.getNom());
jTextFieldFF.setVisible(true);
jTextFieldFV.setVisible(true);
jTextFieldNomP.setVisible(true);
jLabel1.setVisible(true);
jLabel6.setVisible(true);
jLabel7.setVisible(true);
jLabel8.setVisible(true);
jLabel11.setVisible(true);
jButtonSaveP.setVisible(true);
}
});
Here is the code from the add button:
private void jButtonAddPActionPerformed(java.awt.event.ActionEvent evt) {
MoyenPaiement mp = new MoyenPaiement("Nouveau", 0, 0);
((MyListModel) jListPaiement.getModel()).addElement(mp);
jListPaiement.setSelectedValue(mp, true);
jListPaiement.repaint();
}
MyListModel code:
public class MyListModel extends AbstractListModel {
ArrayList list;
public MyListModel(ArrayList list) {
this.list = list;
}
#Override
public int getSize() {
return list.size();
}
#Override
public Object getElementAt(int i) {
return list.get(i);
}
public void addElement(Object o){
list.add(o);
}
public void deleteElement(Object o){
list.remove(o);
}
public void setElement(int i,Object o){
list.set(i, o);
}
public ArrayList getList() {
return list;
}
public void setList(ArrayList list) {
this.list = list;
}
}
Any help will be greatly appreciated.
Thanks
Edit: After further research, the problem is when I add item to my model.
It comes exactly on the line:
((MyListModel) jListPaiement.getModel()).addElement(mp);
Even if I add a simple string such as:
((MyListModel) jListPaiement.getModel()).addElement("String");
The problem still occurs.
Look in detail what happens on this line and if you initialize jListPaiement correctly with the right data.
jListPaiement.setModel(new MyListModel(ls.getDb().getListePaiements()));
Seems like on this line setSelectedValue() can't find the element mp
jListPaiement.setSelectedValue(mp, true);
I finally found a solution.
Rather than using my own List Model, I used DefaultListModel and everything works fine. It's been long time since i worked on this project and I don't remember why i chose to make my own list model class.
Even tough it works now, I still don't understand what was missing in my own class (MyListModel) that made it not working..

Error using custom renderer to show value in jComboBox

I have researched and attempted to fill a jcombobox dynamically from an arraylist containing Publisher Objects. I have tried to implement a renderer in order to show the Publishers name using the getName() method. The combobox shows the names when the program is run, however, if a new Publisher is then added to the ArrayList, the combobox becomes blank.
Creating Model:
public class PublisherComboBoxModel implements ComboBoxModel{
protected List<Publisher> publishers;
public PublisherComboBoxModel(List<Publisher> list) {
this.listeners = new ArrayList();
this.publishers = list;
if(list.size() > 0) {
selected = list.get(0);
}
}
protected Object selected;
#Override
public void setSelectedItem(Object item) {
this.selected = item;
}
#Override
public Object getSelectedItem() {
return this.selected;
}
#Override
public Object getElementAt(int index) {
return publishers.get(index);
}
#Override
public int getSize() {
return publishers.size();
}
protected List listeners;
#Override
public void addListDataListener(ListDataListener l) {
listeners.add(l);
}
#Override
public void removeListDataListener(ListDataListener l) {
this.listeners.remove(l);
}
}
Creating renderer:
jComboBoxPublisher.setModel(publisherComboModel);
jComboBoxPublisher.setRenderer(new DefaultListCellRenderer() {
#Override
public Component getListCellRendererComponent(JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
Publisher publisher = (Publisher)value;
if(value!=null)
{
value = publisher.getName();
}
return super.getListCellRendererComponent(list, value,
index, isSelected, cellHasFocus);
}
This is not necessarily an answer, but highlights a potential problem
While skimming over your code, I noticed that you combo box model is simply maintaining a reference to the original list. This isn't necessarily a problem, but may result in some unexpected and potentially, unwanted behaviour...
The main problem, is that the combo box model has no idea when the list is changed, therefore it can't tell combo box that it should updated.
Generally, what I would normally do is make a new List of the original list. This means that if the original is updated, it won't cause issues for the model and and combo box.
I would then add mutation functionality to the combo box model so it could be updated, for example...
public class PublisherComboBoxModel extends AbstractListModel implements ComboBoxModel {
private List<Publisher> publishers;
private Publisher selectedItem;
public PublisherComboBoxModel(List<Publisher> publishers) {
this.publishers = new ArrayList<>(publishers);
}
public void addPublisher(Publisher pub) {
publishers.add(pub);
fireIntervalAdded(this, publishers.size() - 1, publishers.size() - 1);
}
#Override
public int getSize() {
return publishers.size();
}
#Override
public Object getElementAt(int index) {
return publishers.get(index);
}
#Override
public void setSelectedItem(Object anItem) {
selectedItem = (Publisher) anItem;
}
#Override
public Object getSelectedItem() {
return selectedItem;
}
}
There are several alternatives to this idea. You could create a "general" model, which listed the publishers, but provided event notification to interested parties, so when you added or removed publishers from this model, interested parties, like the combo box model, would be notified and have an opportunity to update themselves and forward appropriate notifications to their interested parties.
Personally, in larger scaled applications, this is my preferred approach.
Another approach would be to provide the combo box model with direct notification...
Thats, you would maintain a reference to the existing list as you are, but the combo box model would have methods that you could call which it could then forward on.

A better way to use a null checked, type casted value?

I'm writing a library similar to AQuery but with a refined syntax for manipulating elements.
Essentially what AQuery does is safely access the view hierarchy and allow you to call subclass methods on objects like ImageView and TextView.
I've written a generic way to use a subclass of View by using the following code:
My Query object is the base object that's used to manipulate the view hierarchy. The basic format looks like this:
public class Query {
private View mView;
// ...
}
Next is the generic interface. This is an inner interface of the Query object:
private interface Operation<T extends View> {
public void execute(T view);
}
Next is the run method in Query. This checks the current node this query represents and calls the execute method on the Operation object if it is successful:
private <T extends View> Query run(Class<T> cls, Operation<T> operation) {
T t = cls.cast(mView);
if (t == null) {
Log.e(TAG, "view is not a " + cls.getSimpleName());
} else {
operation.execute(t);
}
return this;
}
So now that the template code is written, I use methods similar to this to implement functionality:
public Query text(final CharSequence str) {
return run(TextView.class, new Operation<TextView>() {
#Override
public void execute(TextView view) {
view.setText(str);
}
});
}
For every method that modifies the View hierarchy, I have to write this boilerplate-looking code.
Is there any way I can refactor this code to make methods like text simpler?
FYI what you have here isn't really checking the type of mView. Class.cast will throw a ClassCastException if mView is not assignable to type T, so the log message there doesn't actually represent what happens. t == null would be true if and only if mView were null.
It's a little hard to tell what you're trying to achieve without some stubs of what Query will do. If your use would allow parameterization of Query, then you can just make the operation a function of that. This would give you compile-time checks of the view matching the type of the query. e.g.
public interface Query<ViewT extends View> {
void run(ViewT view);
}
public Query<TextView> text(final CharSequence str) {
return new Query<TextView>() {
public void run(TextView view) {
view.setText(str);
}
};
}
If that's not possible (i.e. the view types are never known at compile time) then you can still parameterize the implementation of it and simply perform the action if and only if the argument type matches the query type. e.g.:
public interface Query {
void run(View view);
}
private abstract class TypedQuery<ViewT extends View> implements Query {
private final Class<ViewT> viewType;
private TypedQuery(Class<ViewT> viewType) {
this.viewType = viewType;
}
public final void run(View view) {
if (viewType.isInstance(view)) {
runInternal((ViewT) view);
} else {
Log.e(TAG, "view " + view + " is not a " + viewType.getSimpleName());
}
}
protected abstract void runInternal(ViewT view);
}
public Query text(final CharSequence str) {
return new TypedQuery<TextView>(TextView.class) {
#Override
protected void runInternal(TextView view) {
view.setText(str);
}
};
}

Categories

Resources