Problems with Table.getValue()

Hi all!

There is a behavior in Table.getValue() that i think is a bug, but i would like others to review to confirm or to help me to find a workaround for this issue.

I usually create tables with a BeanContainer as its dataSource, and fill the dataSource with a collection of beans.

All my beans, as they are persisted in a database, have an “id” property, and their methods “hashCode” and “equals” are based just on the value of this property. In other words, two beans are considered equals and have the same hashCode if both have the same id (as both points to the same database record). I presume this is a common design pattern, but i’m pointing it just to make clear how by beans work.

My tables have a “refresh” button, to load the most recent versions of the beans from the database. When a user clicks the “refresh” button, i reload the beans from database, remove all the current itens from the container and add the new beans to it. BUT, i want to keep the items that was selected before the “refresh” operation still selected after it, and i’m facing an odd behavior when the selected record has one of its properties changed in the database.

Lets suppose i have a selected bean with id =1, field1=“value1”, and after the the refresh, this bean was changed to id=1, field1=“value2”.

Before refresh the table, i store the selected item using o = table.getValue(). After the refresh, i restore the previously selected item with table.setValue(o). So far, so good. The table is refreshed, the new item is selected, but if i call table.getValue() again, it returns the old instance of the bean (where field1 was equal to “value1”). While i understand this behavior, as Vaadin is storing the selection just like i passed to it previously, i think when we call table.setValue(selection), instead of just store the received “selection” internally and mark the records selected, Vaadin should reload its internal selection object with the new items acquired from the container.

I wrote a class to show the problem:

package br;

import com.vaadin.annotations.Push;
import com.vaadin.data.util.AbstractBeanContainer.BeanIdResolver;
import com.vaadin.data.util.BeanContainer;
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.Notification;
import com.vaadin.ui.Table;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;

@Push
public class TempUI extends UI {

    private static final long serialVersionUID = 1L;

    @Override
    protected void init(VaadinRequest request) {
        VerticalLayout content = new VerticalLayout();
        content.setSizeFull();

        final BeanContainer<MyBean, MyBean> container = new BeanContainer<>(MyBean.class);
        container.setBeanIdResolver(new BeanIdResolver<MyBean, MyBean>() {

            @Override
            public MyBean getIdForBean(MyBean bean) {
                return bean;
            }

        });

        Button button1 = new Button("Refresh table");
        content.addComponent(button1);

        Button button2 = new Button("Show property");
        content.addComponent(button2);

        final Table table = new Table(null, container);
        table.setSizeFull();
        table.setSelectable(true);
        content.addComponent(table);
        content.setExpandRatio(table, 1);

        MyBean myBean = new MyBean(1, "Old value");

        container.addBean(myBean);

        table.setValue(myBean);

        setContent(content);

        button1.addClickListener(new ClickListener() {

            @Override
            public void buttonClick(ClickEvent event) {
                Object selection = table.getValue();

                container.removeAllItems();
                table.removeAllItems();

                MyBean myBean = new MyBean(1, "New value");
                container.addBean(myBean);

                table.setValue(selection);
            }

        });

        button2.addClickListener(new ClickListener() {

            @Override
            public void buttonClick(ClickEvent event) {
                Notification.show(((MyBean) table.getValue()).getField1());
            }

        });
    }

    public static class MyBean {

        private Integer id;
        private String field1;

        public MyBean(Integer id, String field1) {
            this.id = id;
            this.field1 = field1;
        }

        @Override
        public int hashCode() {
            return id.hashCode();
        }

        @Override
        public boolean equals(Object obj) {
            return obj != null && obj instanceof MyBean && ((MyBean) obj).id.equals(id);
        }

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public String getField1() {
            return field1;
        }

        public void setField1(String field1) {
            this.field1 = field1;
        }

    }

}

Hi Fabiano,

The problems lies in this part of the code:

Object selection = table.getValue();
container.removeAllItems();
table.removeAllItems();
MyBean myBean = new MyBean(1, “New value”);
container.addBean(myBean);
table.setValue(selection);

Hups, wrong button… :slight_smile:

Anyway, the problem lies in the selection. The value of the table component is the itemId of currently selected item. Now that you have the bean itself as an itemId and you use id property in the equals and hashcode methods, the table finds the correct selected item in the container but the value (itemid) might have whatever values in other fields. You could even do like this

Object selection = table.getValue();
((MyBean) selection).setField1("Foobar");
...

The you would see “Foobar” in the notification. To fix issues, you could make sure that the value is always the same bean as it used in the container. In your example, set the value like this

table.setValue(myBean); Anyway, it’s very easy to make a mistake here. I think the best way, in this case, would be to use the id as an item id instead of the bean itself. So create the container like this:

final BeanContainer<Integer, MyBean> container = new BeanContainer<Integer, MyBean>(MyBean.class); And change the other parts of the code accordingly. For example, IdResolver should return the id instead of the bean and getValue() returns now the id instead of the bean.

Thanks, Jarno!

I will try it.

Best regards,

Fabino