Steps to reproduce:
* Create ListView having an initial content of one item, which explicitly is of the value null (not the String "null", but really the Java keyword null).
* Set a custom ListCell factory so it renders empty cells as the String "(empty)" but non-empty cells representing an item of null as the String "(null)".
* At runtime dynamically add non-null items.
Expected result:
* At any time the ListView correctly shows cells either rendered as non-null items, the word "(empty)" for empty space, or the word "(null)" for items that in fact are null (as no further null-items are added, the list will always show one single word instance of "(null)" as the topmost cell).
Actual result:
* Initially the list of nulls is correctly rendered a one top cell showing "(null)" plus some empty cells showing "(empty)". But after adding one or two non-null items, there are more "(null)" cells visible on screen than actually exist.
Cause:
* Cell#isItemChanged does not take care of the empty flag. As a result ListCell#updateItem (ListCell.java:464) does not update a cell when it changes from "empty = true, item = null" to "empty = false, item = null" or vice versa. As a result, the original outdated status is rendered still, which is wrong.
Workaround:
* If application developers need to use null items in ListViews, they should use ListView<Optional<T>> instead of ListView<T>, as the item reference never will be null then.
Solution:
* Modify Cell#isItemChanged from...
return oldItem != null ? !oldItem.equals(newItem) : newItem != null;
...to...
return !(Objects.equals(oldEmpty, newEmpty) && Objects.equals(oldItem, newItem));
...to update the cell in case item still is null but empty flag is modified.
Side effects:
* Corrects the same failure in all kinds of virtual flows, but all invocations have to pass empty flag then.
* Create ListView having an initial content of one item, which explicitly is of the value null (not the String "null", but really the Java keyword null).
* Set a custom ListCell factory so it renders empty cells as the String "(empty)" but non-empty cells representing an item of null as the String "(null)".
* At runtime dynamically add non-null items.
Expected result:
* At any time the ListView correctly shows cells either rendered as non-null items, the word "(empty)" for empty space, or the word "(null)" for items that in fact are null (as no further null-items are added, the list will always show one single word instance of "(null)" as the topmost cell).
Actual result:
* Initially the list of nulls is correctly rendered a one top cell showing "(null)" plus some empty cells showing "(empty)". But after adding one or two non-null items, there are more "(null)" cells visible on screen than actually exist.
Cause:
* Cell#isItemChanged does not take care of the empty flag. As a result ListCell#updateItem (ListCell.java:464) does not update a cell when it changes from "empty = true, item = null" to "empty = false, item = null" or vice versa. As a result, the original outdated status is rendered still, which is wrong.
Workaround:
* If application developers need to use null items in ListViews, they should use ListView<Optional<T>> instead of ListView<T>, as the item reference never will be null then.
Solution:
* Modify Cell#isItemChanged from...
return oldItem != null ? !oldItem.equals(newItem) : newItem != null;
...to...
return !(Objects.equals(oldEmpty, newEmpty) && Objects.equals(oldItem, newItem));
...to update the cell in case item still is null but empty flag is modified.
Side effects:
* Corrects the same failure in all kinds of virtual flows, but all invocations have to pass empty flag then.