How to bind a combobox to an ID value in Vaadin 8

In my code I have:

ComboBox<Vendor> vendorComboBox = new ComboBox<Vendor>();

binder.forField(myForm.vendorComboBox)
    .bind(MyPojo::getVendorID, MyPojo::setVendorID);

Objecting the bind call doesn’t compile because it’s trying to bind a Vendor object to an int ID value. How can I make this link? I don’t care to store the whole Vendor object, just it’s ID.

I’m not exacty sure what you’re trying to do. The binder essentially needs two things, an instance of a component (parameter for forField) and the data instance (parameter for setBean) in order to work. You don’t actually have to keep the reference to the Vendor instance separately, you can store it in the Binder instance with setBean/getBean. I didn’t quite get the ID-part of the question.

Let me try to be more specific. In the example the MyPojo is actually an Invoice object. In the database I only store the vendorID in the Invoice table. More specifically I have:

class Invoice
{
    private int ID;
    private long invoiceNumber;
    ...
    private int vendorID;
}

Now the ComboBox is generated by doing

List<Vendor> vendors = Database.getListOfVendors();
ComboBox<Vendor> vendorComboBox = new ComboBox<Vendor>(vendors);

The problem I’m having is that I would like to bind the Invoice form, specifically the vendorID to the vendorComboBox. I’m struggling to do this because the Invoice object doesn’t actually load a Vendor instance, it only loads the vendorID number. In other words I’m trying to bind the selected Vendor instance from the vendorComboBox to Invoice.vendorID. Everything would be great if the Invoice had Invoice.vendor but instead I have Invoice.vendorID (an int).

So my quesion is how do I bind the selected item in the ComboBox to an int ID rather than the instance object itself?

Use a converter for the field in the binder?

Or are you not in a position where you can refactor to a private Vendor vendor in Invoice?

I would prefer not to use a converter because going from int ID to instance object means a database call to create the Object for something I don’t want or need.

Yes I could possibly fake the object and only populate the ID field leveraging the equals() method but there must be a simpler way no? That seems like a big workaround for something that would be common…

One would think the converter would be pretty trivial since you would have loaded the list of vendors anyway, the solution you’re looking for would be something like a setItemValueGenerator(v → v.getId())(?)

Personally, for list selections etc. I often use a custom ListValue class which has an Integer id and a String label with equals/hashcode on the id that serves a placeholder for some larger entity I just want to simulate picking from a list…

Unfortunately it doesn’t work that way. I do have a list of vendors but I don’t keep a reference to it in the converter, nor should the converter hold a reference to the list of items in the combobox. Therefore all my converter can do is:

@Override
public Vendor convertToPresentation(Integer id, ValueContext context)
{
    // ignore issues with empty selection for example
    return new Vendor(id);
}

If I do this code it won’t work because the ComboBox won’t be selected, even if I override Vendor.equals(). Well it’s selected but not in the GUI because for some reason the mapping is done with:

vendorComboBox.setItemCaptionGenerator(Vendor::getName); My hope was the ComboBox would run an equals on the List of items and then select the appropriate one. However it seems to match on the items’ caption method rather than the equals() method. I confirmed this with system.out statements.

Which means that the only way for me to make it work is to have a call to the database in the VendorConverter so that i can also populate the Vendor’s getName method with data.

I submitted a bug report because it should really use the equals() methods rather than comparing the ItemCaption because this can lead to errors. For example what if instead of vendors it was categories and two categories had the same name but different icons. To be more precise let’s say I was displaying accounting categories and I had two categories both called “late fee” but where one was a revenue and the other was an expense, and they were differentiated visually by the use of the ItemIcon (a green revenue icon versus a red expense icon). There could be a number of other cases such as if have two kids in the same classroom with different names and use their profile picture to differentiate the two in a combobox. The comparison really should NOT be done on the ItemCaption but rather the equals methods of the Item objects.

The bug report can be found at: https://github.com/vaadin/framework/issues/9051

Hopefully it will be fixed soon…

Just to follow up the issue is that you need to override the hashcode method and NOT the equals method for this to work. The internal mechanisms within Vaadin’s combobox code uses the hashcode to select the items and not the equals, therefore if you’re say relying only on the ID value of the object (uuid) then your hashcode should return the ID rather then the default hashcode implementation rather then the equals method.

As a habit, I always override either none or both in pairs.

I wonder if a fictional setItemValueGenerator(Pojo::getId) or setItemValueGenerator(p → p.getId()) could be implemented in a generic way in Vaadin components with some sort of internal converter?

It actually turns out that there appears to be a bug in the Vaadin 8 code related to the combobox which is why I was having so many issues. It wasn’t just an issue with hashcodes but there is also an issue with lists that are longer than the comboboxe’s pagelength.

I submitted a bug with code that can consistently replicate the issue at:
https://github.com/vaadin/framework/issues/9085
Hopefully this will be resolved shortly and will no longer be an issue, but for anyone using Vaadin 8, at least version 8.0.5 and earlier, this may explain why you’re sometimes getting an empty selection on the combobox if your list of items is longer.

Sorry for the necroposting, but I found an “easy” solution for this problem. However, it does require querying the database, so while not a perfect solution to Stephan’s problem, it could work for others (which is why I’m posting this).

binder.forField(field).withConverter((v -> extractID(v)), v -> findTuple(service, v).bind("field");
private Integer extractID(Tuple v) {
	return (v == null) ? null : v.getID();
}

private Tuple findTuple(Service service, Integer id) {
	// User selected blank, return blank
	if (id == null)
		return new Tuple();
	// Change this to properly interface with your service
	return service.getTuple(id);
}

Yes, I know it’s another database call, but it seems pretty lightweight. And if you really need to reduce your database calls, you could probably have your service “cache” the results from previous calls or something. But there’s no way to get from the object’s id to the object itself without calling the database at some point.

Hope this helps [lost wanderers like me]
(https://xkcd.com/979/).