Issue
I need to get two specific fields of every object inside an ObservableList, multiply them and get the sum of all their products. I also need to keep the sum updated
For now I'm trying to get the sum of only one of those fields first (just because its easier and might help me to understand what is happening).
I have discovered:
which is almost what I'm trying to do. However, applying the same code will throw a NullPointerException. Which I assume it's because there was more behind the scene.
Also, according to:
the observableList needs to have and extractor so it can track the updates of the required field on each element.
Here is what I tried, only ONE field first!!:
// Graphical stuff
void createExempleMenu(){
ObservableList<Itens> orc = FXCollections.observableArrayList(o -> new Observable[] {o.valueProperty()});
Label valueFinalLabel;
valueFinalLabel.textProperty().bind( // this gets NullPointerException
Bindings.createObjectBinding(() ->{
if(orc==null || orc.isEmpty())
return BigDecimal.ZERO; // case orc is empty
else
return orc.stream().map(i->i.getValue()).reduce(BigDecimal.ZERO,BigDecimal::add);},
orc).asString()
);
}
// item
class Itens {
private ObjectProperty<BigDecimal> value;
public BigDecimal getValue() {
return value.get();
}
public void setValue(BigDecimal b){
value.set(b);
}
public void valueProperty(BigDecimal b){
return value;
}
}
Solution
You have a few issues with the Itens
class:
- You never initialize
value
. - The method
valueProperty(BigDecimal b)
must returnObjectProperty<BigDecimal>
instead ofvoid
. - You have a typo in method
getValue()
, instead of returningvalue.get()
you are returningvalor.get()
.
class Itens {
private final ObjectProperty<BigDecimal> value =
new SimpleObjectProperty<>(this, "value");
Itens(BigDecimal value) {
this.value.set(value);
}
public BigDecimal getValue() {
return valueProperty().get();
}
public void setValue(BigDecimal value){
valueProperty().set(value);
}
public ObjectProperty<BigDecimal> valueProperty(){
return value;
}
}
Also, you never initialize the Label
.
Finally, you can improve your Bindings
:
- You can create a
StringBinding
directly instead of creating anObjectBinding
and then creatingStringBinding
. orc
is nevernull
, so you don't need to check fornull
.- You don't need to check for
orc.isEmpty()
because when you reduce thestream
you are supplying an identity `BigDecimal.ZERO
Label valueFinalLabel = new Label();
ObservableList<Itens> orc = FXCollections.observableArrayList(
obs -> new Observable[] {obs.valueProperty()});
valueFinalLabel.textProperty().bind(
Bindings.createStringBinding(() -> orc.stream()
.filter(Objects::nonNull)
.map(Itens::getValue)
.reduce(BigDecimal.ZERO, BigDecimal::add).toString(), orc));
Test:
valueFinalLabel.textProperty().addListener((obs, oldVal, newVal) ->
System.out.println(String.format("Label change from %s to %s",
oldVal, newVal)));
Itens i1 = new Itens(BigDecimal.valueOf(1));
Itens i2 = new Itens(BigDecimal.valueOf(2));
Itens i3 = new Itens(BigDecimal.valueOf(3));
orc.add(i1);
orc.add(i2);
orc.add(i3);
i1.setValue(BigDecimal.valueOf(4));
Output:
Label change from 0 to 1
Label change from 1 to 3
Label change from 3 to 6
Label change from 6 to 9
Answered By - Oboe
Answer Checked By - Marie Seifert (JavaFixing Admin)