Short Question: What is the best way to populate a Java FX table view with data from a database by using a record? (rather than a typical object class with getters and setters)
Full question: I have this InventoryRecord.java file that I recently created to replace a traditional Object called "Inventory" that I used to populate a tableview:
import java.util.List;
public record InventoryRecord(
String make
, String model
, String officialModel
, String upc
, List<String> category
, String type
, String description
,String vendor
, Double cost
, Double price
) {}
I would like to use this record as my framework to populate my TableView using Data from a Database (in this case its Neo4j but that's not important)
Before I had this InventoryRecord record, I was using a typical class called Inventory that looked like this (I have shortened it of course to keep the question readable, assume there are the getters/setters for each property of Inventory.)
public class Inventory {
private String make;
private String model;
private String officialModel;
private List<String> category;
private String type;
private String description;
private String vendor;
private Double cost;
private Double price;
private String upc;
//Method for actual instantiation of an Inventory Object, uses setter methods in order to perform validations for each field
public Inventory(String make, String model, String officialModel, String upc, List<String> category, String type, String description,String vendor, Double cost, Double price) {
this.setMake(make);
this.setModel(model);
this.setOfficialModel(officialModel);
this.setUpc(upc);
this.setCategory(category);
this.setType(type);
this.setDescription(description);
this.setVendor(vendor);
this.setCost(cost);
this.setPrice(price);
}
And this was how I populated the table with the query results
while (result.hasNext()) {
//result.next() serves as our iterator that increments us through the records and through the while loop
Record record = result.next();
String make = record.get("make").asString();
String model = record.get("model").asString();
String officialModel = record.get("officialModel").asString();
String upc = record.get("upc").asString();
List<String> category = record.get("categories").asList(Value::asString);
String type = record.get("type").asString();
String desc = record.get("description").asString();
String vendor = record.get("vendor").asString();
Double cost = record.get("cost").isNull() ? null : record.get("cost").asDouble();
Double price = record.get("price").isNull() ? null : record.get("price").asDouble();
//when creating the anonymous Inventory items, you can send them to the constructor with any name
inventoryTable.getItems().add(new Inventory(make, model, officialModel,upc, category, type, desc,vendor, cost, price));
}
//The property value factory must use the property names as string arguments in the constructor
makeColumn.setCellValueFactory(new PropertyValueFactory<>("make"));
modelColumn.setCellValueFactory(new PropertyValueFactory<>("model"));
officialModelColumn.setCellValueFactory(new PropertyValueFactory<>("officialModel"));
categoryColumn.setCellValueFactory(new PropertyValueFactory<>("category"));
upcColumn.setCellValueFactory(new PropertyValueFactory<>("upc"));
typeColumn.setCellValueFactory(new PropertyValueFactory<>("type"));
descriptionColumn.setCellValueFactory(new PropertyValueFactory<>("description"));
vendorColumn.setCellValueFactory(new PropertyValueFactory<>("vendor"));
costColumn.setCellValueFactory(new PropertyValueFactory<>("cost"));
priceColumn.setCellValueFactory(new PropertyValueFactory<>("price"));
inventoryTable.setItems(inventoryTable.getItems());
But how can I switch everything to a InventoryRecord rather than just the Inventory object, the .setCellValueFactory and PropertyValueFactory don't like what they see.
I tried this (with some help from Google Bard) and it seems to not give any errorsfor the strings:
makeColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().make()));
But for the List and Double properties/columns I tried this:
categoryColumn.setCellValueFactory(cellData -> new SimpleListProperty<List<String>>(cellData.getValue().category()));
costColumn.setCellValueFactory(cellData -> new SimpleDoubleProperty(cellData.getValue().cost()));
and I get these errors: Type mismatch: cannot convert from SimpleListProperty<List<String>> to ObservableValue<List<String>> Type mismatch: cannot convert from SimpleDoubleProperty to ObservableValue<Double>
Any help is much, much appreciated!
I tried this:
while (result.hasNext()) {
//result.next() serves as our iterator that increments us through the records and through the while loop
Record record = result.next();
String make = record.get("make").asString();
String model = record.get("model").asString();
String officialModel = record.get("officialModel").asString();
String upc = record.get("upc").asString();
List<String> category = record.get("categories").asList(Value::asString);
String type = record.get("type").asString();
String desc = record.get("description").asString();
String vendor = record.get("vendor").asString();
Double cost = record.get("cost").isNull() ? null : record.get("cost").asDouble();
Double price = record.get("price").isNull() ? null : record.get("price").asDouble();
// Create an InventoryRecord directly from the Record
InventoryRecord inventoryRecord = new InventoryRecord(make,model,officialModel,upc,category,type,desc,vendor,cost,price);
// Add the InventoryRecord to the table items
inventoryTable.getItems().add(inventoryRecord);
}
//The property value factory must use the property names as string arguments in the constructor
makeColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().make()));
modelColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().model()));
officialModelColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().officialModel()));
categoryColumn.setCellValueFactory(cellData -> new SimpleListProperty<List<String>>(cellData.getValue().category()));
upcColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().upc()));
typeColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().type()));
descriptionColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().description()));
vendorColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().vendor()));
costColumn.setCellValueFactory(cellData -> new SimpleDoubleProperty(cellData.getValue().cost()));
priceColumn.setCellValueFactory(cellData -> new SimpleDoubleProperty(cellData.getValue().price()));
inventoryTable.setItems(inventoryTable.getItems());
but got these errors: Type mismatch: cannot convert from SimpleListProperty<List<String>> to ObservableValue<List<String>> Type mismatch: cannot convert from SimpleDoubleProperty to ObservableValue<Double>
I expect something like this (This is how it worked with my Inventory class, the one I'm trying to replace with InventoryRecord): TableView using Inventory Class
The
PropertyValueFactoryclass will not work with records, at least not as of JavaFX 21, but you've already figured that out (I recommend avoiding that class regardless). The errors you're getting are not directly related to you changing the class to a record. They're caused by returning the wrong type in a few of your cell-value factories.A
SimpleListProperty<E>is already a list ofE. By parameterizing it withList<String>you are saying you have a list of lists of string. And based on the error you're getting, that's not what the column's value type is. Return aSimpleObjectProperty<List<String>>from the appropriate cell value factory instead.Additionally, a
SimpleDoublePropertyis anObservableValue<Number>. But from the error you're getting, you actually need to return anObservableValue<Double>. Change the appropraite cell value factories to return aSimpleObjectProperty<Double>instead. The other option is to change the column's value type fromDoubletoNumber. I recommend the former approach, however.To simplify this somewhat, you can create a utility method in your class. Something like:
Which would let you define all the cell value factories like so: