Inserting integer (not String) data into a JavaFX2 TableView - java

so I've got a table working properly and grabbing data from an ObservableList with the code here:
public void setMainTableData(ObservableList<FileMP3> list)
{
artistCol.setCellValueFactory(new PropertyValueFactory<FileMP3, String>("artist"));
albumCol.setCellValueFactory(new PropertyValueFactory<FileMP3, String>("album"));
titleCol.setCellValueFactory(new PropertyValueFactory<FileMP3, String>("title"));
trackCol.setCellValueFactory(new PropertyValueFactory<FileMP3, String>("track"));
yearCol.setCellValueFactory(new PropertyValueFactory<FileMP3, String>("year"));
mainTable.setItems(list);
}
These columns, however do not ALL contain string data - I need to able to insert an int, and potentially other types like Duration. The track and year entries are stored as integers, and there is a (not shown) entry called length. This is stored in my FileMP3 object as a Duration, and I don't see any obvious way to manipulate the data stored there before inserting it into the table. I'd like to be able to use Duration.getMillis() and then perform some math on that to get it into a displayable int format, but I want to keep it stored in the FileMP3 as Duration.
All the tutorials I've read on the topic all use the constructor as such:
new PropertyValueFactory<FileMP3, String>("genre")
All in all, I'd like to be able to insert something other than a String into the table.

You can just replace String with any (reference, not primitive) type. For example:
TableColumn<FileMP3, Integer> yearCol = new TableColumn<>("Year");
yearCol.setCellValueFatory(new PropertyValueFactory<FileMP3, Integer>("year"));
Similarly with Duration (instead of Integer).
By default, the value in the cell will be displayed by calling toString() on the value in the cell. If you want the value to be displayed differently, you can create a custom cell factory (different to a cell value factory):
TableColumn<FileMP3, Integer> durationCol = new TableColumn<>("Duration");
durationCol.setCellValueFactory(new PropertyValueFactory<FileMP3, Duration>("duration"));
durationCol.setCellFactory(new Callback<TableColumn<FileMP3, Duration>, TableCell<FileMP3, Duration>>() {
#Override
public TableCell<FileMP3, Duration> call(TableColumn<FileMP3, Duration> col) {
return new TableCell<FileMP3, Duration>() {
#Override
protected void updateItem(Duration duration, boolean empty) {
super.updateItem(duration, empty);
if (empty) {
setText(null);
} else {
setText(Double.toString(duration.toMillis());
}
}
};
}
});

You can provide a custom cell value factory:
duration.setCellValueFactory(new Callback<CellDataFeatures<FileMP3, Integer>, ObservableValue<Integer>>() {
#Override public ObservableValue<Integer> call(CellDataFeatures<FileMP3, Integer> c) {
return new SimpleIntegerProperty(c.getValue().getDurationAsInt()));
}
});

Related

Cannot seem to write custom object to Firebase database?

I'm working on a project using Firebase, which I've never used before, and I know almost nothing about Firebase itself, as the rest of my team has been responsible for most of the dealings with it. I'm writing a parser for some Excel data where I need to extract some specific data and then upload it to Firebase. The parsing is done, but I'm having trouble writing it to Firebase.
We have a sub-database called "families" in our root database that I need to write this data to. I have a class called RegistrationSheet which contains all the data in this particular spreadsheet broken up into objects to represent the structure of the JSON. I'm aware that you can write custom objects to the Firebase database and it will be converted to a JSON format that represents that data. I found a page detailing the different data types that can be written to the database and converted to JSON, and among them were Map and List. So here are my classes that represent the "families" database.
RegistrationSheet.java:
public class RegistrationSheet {
public List<Object> families;
public void addFamily(Family f) { families.add(f); }
public RegistrationSheet() {
families = new ArrayList<>();
}
public void writeToFirebase() {
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("families");
ref.removeValue(); // Delete the data currently in the database so we can rewrite it.
ref.setValue(families);
}
public File outputJSON() {
return null;
}
}
Family.java:
public class Family {
public Map<String, Object> fields;
public void addField(String str, Object obj) { fields.put(str, obj); }
public Family() {
fields = new HashMap<>();
}
}
Child.java:
public class Child {
public Map<String, Object> fields;
public void addField(String str, Object obj) { fields.put(str, obj); }
public Child() {
fields = new HashMap<>();
}
}
The families list contains Family objects, and one of the "fields" that can be added to the Map in the Family objects is a List of Child objects. I figured that because these are all objects that are valid to write to Firebase, that simply writing the "families" list in the RegistrationSheet object would be enough:
public void writeToFirebase() {
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("families");
ref.removeValue(); // Delete the data currently in the database so we can rewrite it.
ref.setValue(families);
}
Is there something wrong with the structure of any of my classes or how I'm writing the data to Firebase? Because after executing this, the "families" sub-database disappears from Firebase and I have to restore it from my backup. It seems I have the correct DatabaseReference since removeValue() seems to be removing it, but why isn't it then writing the data from the families list?
I would appreciate any help that someone could provide.
Try the following code. It gives you the reason as to why it is not writing the value.
public void writeToFirebase() {
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("families");
ref.removeValue(); // Delete the data currently in the database so we can rewrite it.
ref.setValue(object, new DatabaseReference.CompletionListener() {
#Override
public void onComplete(DatabaseError databaseError, DatabaseReference reference) {
if (databaseError != null) {
Log.e(TAG, "Failed to write message", databaseError.toException());
}
}
});
}
Then you can debug your code based on the Exception generated

Is it possible to write a jOOQ Converter to apply to an array?

I have a postgres database with some columns that are of type varchar[]. jOOQ and pgjdbc-ng aren't playing along nicely; jOOQ's DefaultBindContext has something along the lines of:
protected final BindContext bindValue0(Object value, Field<?> field) throws SQLException {
SQLDialect dialect = configuration.dialect();
// [#650] [#3108] Use the Field's Converter before actually binding any value
Converter<?, ?> converter = field.getConverter();
Class<?> type = converter.fromType();
value = ((Converter) converter).to(value);
//...
else if (type.isArray()) {
switch (dialect) {
case POSTGRES: {
stmt.setString(nextIndex(), toPGArrayString((Object[]) value));
break;
}
Which sets the statement variable to "{\"value1\", \"value2\", \"etc\"}", which is how you'd specify an array in a query. Later on, pgjdbc-ng has:
public static Object coerceToArray(Format format, Object val, Type type, Class<?> targetType, Map<String, Class<?>> typeMap, PGConnectionImpl connection) throws SQLException {
if (val == null) {
return null;
}
else if (val instanceof PGArray) {
return coerceToArray(format, ((PGArray) val).getValue(), type, targetType, typeMap, connection);
}
else if (val.getClass().isArray()) {
return coerceToArray(format, val, 0, Array.getLength(val), type, targetType, typeMap, connection);
}
throw createCoercionException(val.getClass(), targetType);
}
Which expects that the value on the statement will be of either type PGArray or an actual array; it fails to coerce the String representation of an array into a String representation of an array. :(
I am trying to write a jOOQ Converter that will convert between String[] and PGArray; ideally, this would mean that jOOQ's DefaultBindContext would leave the converted value well enough alone, and then pgjdbc-ng would be able to handle it correctly.
However, I have been unable to write a jOOQ schema configuration that allows me to do this. I've tried variations on:
<customType>
<customType>
<name>StringArray</name>
<type>java.lang.String[]</type>
<converter>my.package.PGStringArrayConverter</converter>
</customType>
</customTypes>
<forcedTypes>
<forcedType>
<name>StringArray</name>
<types>varchar\[\]</types>
</forcedType>
</forcedTypes>
Without having any luck; the generated table objects refer to a String[][], and varchar[] doesn't match on anything. Even if I break it down, so that the forcedType matches on any type but with an <expression> that only matches my column, and the Converter's type is java.lang.String, I end up with the java compiler complaining about being unable to cast Object[] to String[].
Is there any light at the end of this tunnel, or should I start looking to migrate my database?
So, it turns out the answer is, "kinda."
I was creating my PGStringArrayConverter class backwards; I had created
public abstract class PGArrayConverter implements Converter<String[], PGArray>
What ended up working was:
public abstract class PGArrayConverter implements Converter<Object, String[]> {
#Override
public String[] from(final Object databaseObject) {
if (databaseObject == null) {
return null;
}
if (databaseObject.getClass().isArray()) {
return (String[]) databaseObject;
}
return (String[]) ((PGArray)databaseObject).getValue();
}
#Override
public Object to(final String[] userObject) {
if (userObject == null) {
return null;
}
return new PGArray(null, null, userObject);
}
#Override
public Class<Object> fromType() {
return Object.class;
}
#Override
public Class<String[]> toType() {
return String[].class;
}
}
from() turned out kind of odd; I was getting arrays of Strings, rather than PGArrays. I don't think the database was aware that it was "supposed to" serialize them into PGArrays.
I also had to modify the generated code, which is slightly obnoxious. My schema.xml file contained:
<customType>
<name>StringArray</name>
<type>java.lang.String</type>
<converter>my.package.PGStringArrayConverter</converter>
</customType>
<forcedType>
<name>StringArray</name>
<expression>.*tabular_answer_entry.text.*</expression>
<types>.*</types>
</forcedType>
And I had to change the generated table files to remove the .getArrayDataType() call on the createField's data type:
public final org.jooq.TableField<my.package.gen.tables.records.TabularAnswerEntryRecord, java.lang.String[]> TEXT = createField("text", org.jooq.impl.DefaultDataType.getDefaultDataType("java.lang.String").getArrayDataType(), this, "", new my.package.PGStringArrayConverter());
to:
public final org.jooq.TableField<my.package.gen.tables.records.TabularAnswerEntryRecord, java.lang.String[]> TEXT = createField("text", org.jooq.impl.DefaultDataType.getDefaultDataType("java.lang.String"), this, "", new my.package.PGStringArrayConverter());
The whole solution feels kind of hacky, and I am going to have to write a monster of a unit test to make sure that this doesn't break when we update any of the relevant packages, but at least I am able to read from and write to my database.

SetCellValueFactory to data object in JavaFX TableColumn

I have a tablecolumn with custom cell render this cell render takes an object and renders its properties as Labels. The problem is that I can't find a way to pass the same object in the arraylist to a column. Here is my code:
//I want to render this object in a column as well as use it in the rest of columns
CustomerCreationFlow cflow=new CustomerCreationFlow();
cflow.setId(10L);
cflow.setFirstName("Feras");
cflow.setLastName("Odeh");
cflow.setCustomerType("type");
ObservableList<CustomerCreationFlow> data = FXCollections.observableArrayList(cflow);
idclm.setCellValueFactory(new PropertyValueFactory<CustomerCreationFlow, String>("id"));
//I tried this but it didn't work
flowclm.setCellValueFactory(new PropertyValueFactory<CustomerCreationFlow, CustomerCreationFlow>("this"));
typeclm.setCellValueFactory(new PropertyValueFactory<CustomerCreationFlow, String>("customerType"));
flowTable.setItems(data);
Any Suggestion?
You should implement your custom CellFactory by extending TableCell.
In your custom TableCell, you can get the value of the line of the table (logically CustomerCreationFlow) by getting the TableRow of the current TableCell.
That gives:
class MyTableCell<S,T> extends TableCell<S, T>
#Override
public void updateItem(final T item, final boolean empty) {
super.updateItem(item, empty);
if (empty) {
this.setText(null);
this.setGraphic(null);
} else {
S item = (S) this.getTableRow().getItem();
// DO STUFF HERE
}
}
}
T is the type of the data defined by CellValueFactory. S is the type of the data representing a row.

Strange TableView.itemsProperty Behavior?

Let's say I'll add a ChangeListener to a TableView's itemsProperty. When would the ChangeListener's changed method be called?
I tried adding to the empty List where the TableView's items points. The result - The ChangeListener's changed method didn't get called.
tableView.itemsProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue ov, Object t, Object t1) {
System.out.println("Changed!");
}
});
final ObservableList data = FXCollections.observableArrayList(new ArrayList());
data.clear();
//data.add(new Object()); don't call this yet
tableView.setItems(data);
data.clear();
data.add(new Object());
tableView.setItems(data);
However, I also tried adding to an empty List and then let TableView's items point on it. The result - The ChangeListener's changed method got called.
tableView.itemsProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue ov, Object t, Object t1) {
System.out.println("Changed!");
}
});
final ObservableList data = FXCollections.observableArrayList(new ArrayList());
data.clear();
data.add(new Object());
tableView.setItems(data);
data.clear();
data.add(new Object());
tableView.setItems(data);
I looked it up on http://docs.oracle.com/javafx/2/api/javafx/scene/control/TableView.html#itemsProperty() but it only says "The underlying data model for the TableView. Note that it has a generic type that must match the type of the TableView itself."
I'm asking this because I might miss out on some other important circumstances.
A not fully documented fact (aka: implementation detail) is that ObjectProperty only fires on
!oldValue.equals(newValue); // modulo null checking
That's critical for a list-valued object property, as lists are specified to be equal if all their elements are equal. In particular, all empty lists are equal to each other, thus replacing one empty list by another empty list as in your first snippet will not make the property fire:
// items empty initially
TableView table = new TableView()
table.itemsProperty().addListener(....)
ObservableList empty = FXCollections.observableArrayList();
// replace initial empty list by new empty list
table.setItems(empty);
// no change event was fired!
That's nasty if your code wants to listen to changes of the content - it would need to re-wire the ListChangeListeners whenever the identity of the items value changes but can't with a changeListener because that fires based on equality. BTW, even fx-internal code got that wrong and hot-fixed by a dirty hack
And no nice solution available, just a couple of suboptimal options
use an InvalidationListener instead of a changeListener
bind (unidirectionally!) a ListProperty to the list-valued object property and listen to the latter
use an adapter that combines the above to at least have it out off the way
A code snippet I use:
public static <T> ListProperty<T> listProperty(final Property<ObservableList<T>> property) {
Objects.requireNonNull(property, "property must not be null");
ListProperty<T> adapter = new ListPropertyBase<T>() {
// PENDING JW: need weakListener?
private InvalidationListener hack15793;
{
Bindings.bindBidirectional(this, property);
hack15793 = o -> {
ObservableList<T> newItems =property.getValue();
ObservableList<T> oldItems = get();
// force rewiring to new list if equals
boolean changedEquals = (newItems != null) && (oldItems != null)
&& newItems.equals(oldItems);
if (changedEquals) {
set(newItems);
}
};
property.addListener(hack15793);
}
#Override
public Object getBean() {
return null; // virtual property, no bean
}
#Override
public String getName() {
return property.getName();
}
#Override
protected void finalize() throws Throwable {
try {
Bindings.unbindBidirectional(property, this);
property.removeListener(hack15793);
} finally {
super.finalize();
}
}
};
return adapter;
}
TableView does not have implemented the method add view documentation
My aproach is the following:
To initialize the TableView itemList:
tableX.setItems(itemListX);
you could also initialize it by using the default list of the TableView:
tableX.getItems.addAll(itemListX);
in this case it will copy the list.
And the aproach to add items dynamically:
1-If you still have a reference to itemListX:
itemListX.add(item);
this you will update the TableView since the table observes the ObservableList itemListX.
2-Else, if you dont any more:
tableX.getItems().add(item);

Trouble with CellList empty widget and AsyncDataProvider

I have a CellList that I'm populating with an AsyncDataProvider:
#UiField(provided = true)
CellList<PlayerDataEntity> friendCellList;
#Inject
FriendListViewImpl(FriendListController controller) {
friendCellList = new CellList<PlayerDataEntity>(new PlayerCell());
initWidget(uiBinder.createAndBindUi(this));
// if we don't set the row data to empty before trying to get the real data,
// the empty list widget will never appear. But if we do set this,
// then the real data won't show up.
friendCellList.setRowData(Lists.<PlayerDataEntity>newArrayList());
new AsyncDataProvider<PlayerDataEntity>() {
#Override
protected void onRangeChanged(final HasData<PlayerDataEntity> display) {
rpcService.getPlayers(new AsyncCallback<List<PlayerDataEntity>>() {
#Override
public void onSuccess(List<PlayerDataEntity> result) {
display.setRowData(0, result);
}
});
}
}.addDataDisplay(friendCellList);
friendCellList.setEmptyListWidget(new Label("No friends found"));
}
If I don't initially set the row data to an empty list, then the empty list widget won't show up if the RPC service returns an empty list. However, if I do initially set the row data to the an empty list, and the RPC returns a non-empty list, that data won't show up in the widget.
I must be misunderstanding some aspect of the CellList's API. What am I doing wrong?
If you know, that the total list (not just the list in the range) is empty, then you can set display.setRowCount(0), so the CellList will display "No friends found".
If your service always returns the entire list, this can be done easily like
public void onSuccess(List<PlayerDataEntity> result) {
display.setRowCount(result.size());
display.setRowData(0, result);
}
Add the following line to the method:
#Override
public void onSuccess(List<PlayerDataEntity> result) {
display.setRowData(0, result);
((CellList<PlayerDataEntity>) display).redraw();
}

Categories