MFXListView always recreates cells instance

#393 · closed · 12 comments

View on GitHub ↗

Lairon1

**Describe the bug** I use MFXListView with custom cells and cell contents. Basically an icon and 2 labels. My ListView is stretched to the entire screen horizontally. When I change the size of my window, the height of the ListView changes, but when the height of the ListView changes, for some reason it redraws all the old Cells and creates new ones. Because of this, my list starts to flicker and create a bunch of new objects, which is bad for RAM, and I also use FXML for the cell contents, and constantly loading FXML to change the height of the ListView even by one pixel is not very good. The problem also appears when the list is reduced, although it costs nothing to just delete only unnecessary Cells, but it recreates everything. You can replicate the issues by simply creating a list with custom Cells and trying to change its height. **Expected behavior** I expect my cells to be stored in some pool and reused each time it is needed. But not to create a new MFXListCell each time. ![1-14-2025 (14-29-32)](https://github.com/user-attachments/assets/98ec1443-1de0-4f0a-b09f-71091b037a48) In the console you can see how the logs about creating new RemoteModView are running. This is the content of my Cell

Comments

palexdev

1) It's not the library, it's probably a bad implementation on your end. The old component (which is MFXListView) updates all the cells in the viewport when events like this happen. It's not creating new cells, otherwise you would not see the icons either, if you look closely only the text is being recomputed. What you want to do is change the way cells are updated, you need to ensure the update is really needed. ```java @Override public void updateItem(T newItem) { T oldItem = getItem(); // Perform identity or equality check depending on your model // e.g. if (Objects.equals(oldItem, newItem)) return; // Do not update if same item setItem(newItem); // Update only if different } ``` 2) I suggest you using the newer implementation which can be found [here](https://github.com/palexdev/VirtualizedFX), beware you'll need Java 21. It's much more reliable and performant.

Lairon1

> 1. It's not the library, it's probably a bad implementation on your end. The old component (which is MFXListView) updates all the cells in the viewport when events like this happen. It's not creating new cells, otherwise you would not see the icons either, if you look closely only the text is being recomputed. What you want to do is change the way cells are updated, you need to ensure the update is really needed. > > ```java > @Override > public void updateItem(T newItem) { > T oldItem = getItem(); > // Perform identity or equality check depending on your model > // e.g. if (Objects.equals(oldItem, newItem)) return; // Do not update if same item > setItem(newItem); // Update only if different > } > ``` > > 2. I suggest you using the newer implementation which can be found [here](https://github.com/palexdev/VirtualizedFX), beware you'll need Java 21. It's much more reliable and performant. My implementation looks like this: Get all data from the server -> Download image -> Collect ready data into one object -> Display data. That is, at the moment of drawing the cell, I simply set ready values ​​in the fields. Why the images do not flicker, I do not know. Here is another example in which I use the same principle, but the images flicker too ![1-14-2025 (14-46-11)](https://github.com/user-attachments/assets/bc1f7a04-d43a-41ef-8411-aeb9ed15199e) Thanks for the tip about VirtualizedFX!

Lairon1

![image](https://github.com/user-attachments/assets/d3fe1362-5457-4f60-aa64-af5a89aca329) ![image](https://github.com/user-attachments/assets/4a83554a-049b-4d0f-b750-d20a7ae6ad45) New cells are created, that is, my implementation has absolutely nothing to do with it.

palexdev

That's so weird, it shouldn't happen. When the height changes a method runs to ensure there are just enough cells to fill the viewport. New cells are created only when there aren't enough or the old state is empty. None of these cases apply to you though, so, I don't know what's going on. Anyway, like I said, use VirtualizedFX. It's a little harder to use because it offers the building blocks for a list view, but it's easy to assemble and offers some defaults too (Although you may want to make your own cells of you don't want to deal with the extra complexity). You can find some examples in the tests. A little preview: ```java VFXList<T, C> list = new VFXList<>( // Model list // Cell factory ); // Wrap in a custom scroll pane VFXScrollPane vsp = list.makeScrollable(); // That's it, add the vsp to your view ```

Lairon1

We won't be able to implement VirtualizedFX, because MaterialFX already uses VirtualizedFX but an older version. And when I try to connect VirtualizedFX everything crashes) And pulling MaterialFX out of the project is not an option because there is a lot of work. @palexdev will it be possible to somehow make friends between MFX and VFX of the new versions?

palexdev

> We won't be able to implement VirtualizedFX, because MaterialFX already uses VirtualizedFX but an older version. And when I try to connect VirtualizedFX everything crashes) And pulling MaterialFX out of the project is not an option because there is a lot of work. @palexdev will it be possible to somehow make friends between MFX and VFX of the new versions? Unfortunately no. If you use combo boxes or any other component using the virtual flow then you can't exclude the dependency. I can't even update MaterialFX as the new VirtualizedFX requires Java 21 Is the code available somewhere? I can try debugging it if you want, see if I can fix the old library

Lairon1

Unfortunately, the project is private, so there is no public access to the code. However, we would still appreciate your support. Would you be open to assisting us via a screen-sharing session during a call? Alternatively, we could set up an IntelliJ IDEA "Code with Me" session so that you can take a closer look.

palexdev

> Unfortunately, the project is private, so there is no public access to the code. However, we would still appreciate your support. Would you be open to assisting us via a screen-sharing session during a call? Alternatively, we could set up an IntelliJ IDEA "Code with Me" session so that you can take a closer look. Let's go with "Code With Me", my pronunciation is not very good, I'm more comfortable writing

Lairon1

Can we go to Discord to chat? I will send you "Code with me" session link. My discord: `0xlairon1`

0xD3ADCODE

@Lairon1 If the problem still exists in your code, here is the solution. I needed a horizontal list and found that MFXListView can't create that type, even though all the code around that feature is already implemented. So I checked the underlying code and came up with a rather creepy solution (because of one small final field). After that, I noticed this strange bug with cell recreation, and it turned out to be in the OrientationHelper implementation. For HorizontalHelper, there is one specific line: ```java this.widthChanged = (observable, oldValue, newValue) -> container.reset(); ``` This line constantly recreates _all_ cells on each width change (so, on each pulse, I guess). In VerticalHelper, there is a similar line ```java this.heightChanged = (observable, oldValue, newValue) -> container.reset(); ``` So, I combined my solution with the new one This extended MFXListView can be used as a drop-in replacement for MFXListView. It supports orientation changes and has a smart debouncer for cell recreation. It has one limitation: if a node is expanding, there may be some empty space because of the debouncer. But this is still better than lags and cell recreation on each frame. **! Warning !** This code is for **Java 23** with **Manifold**! You can replace all `.jailbreak()` calls to reflection ```java public class MFXListViewExt<T> extends MFXListView<T> { private final ObjectProperty<Orientation> orientation = new SimpleObjectProperty<>(null); public MFXListViewExt() { super(); bindOrientation(); } public MFXListViewExt(ObservableList<T> items) { super(items); bindOrientation(); } @SuppressWarnings({"rawtypes", "unchecked", "deprecation"}) private void bindOrientation() { SimpleVirtualFlow virtualFlow = super.jailbreak().virtualFlow; virtualFlow.orientationProperty().bind(orientation); orientation.addListener((_, _, _) -> { OrientationHelper orientationHelper = virtualFlow.jailbreak().getOrientationHelper(); if (orientationHelper instanceof OrientationHelper.HorizontalHelper helper) { replaceListener( helper, virtualFlow.getVirtualFlow().widthProperty(), helper.jailbreak().widthChanged, changeListener -> helper.jailbreak().widthChanged = changeListener ); } else if (orientationHelper instanceof OrientationHelper.VerticalHelper helper) { replaceListener( helper, virtualFlow.getVirtualFlow().heightProperty(), helper.jailbreak().heightChanged, changeListener -> helper.jailbreak().heightChanged = changeListener ); } }); } @SuppressWarnings("deprecation") private void replaceListener(OrientationHelper helper, ReadOnlyDoubleProperty property, ChangeListener<? super Number> resetListener, Consumer<ChangeListener<? super Number>> fieldUpdater) { property.removeListener(resetListener); PauseTransition debounceTimer = new PauseTransition(Duration.millis(200)); debounceTimer.setOnFinished(_ -> resetListener.changed(property, property.getValue(), property.getValue())); ChangeListener<Number> smartListener = (_, _, _) -> { int neededCells = helper.computeCellsNumber(); int actualItems = getItems().size(); int required = Math.min(neededCells, actualItems); int currentCells = getCells().size(); if (currentCells < required) { debounceTimer.playFromStart(); } else { debounceTimer.stop(); } }; fieldUpdater.accept(smartListener); property.addListener(smartListener); } public Orientation getOrientation() { return orientation.get(); } public ObjectProperty<Orientation> orientationProperty() { return orientation; } public void setOrientation(Orientation orientation) { this.orientation.set(orientation); } } ```

palexdev

I fixed it by upgrading the project to JDK 21 and VirtualizedFX 21.x.x You can look at the legacy-21 branch

0xD3ADCODE

@palexdev oh, i see. ty But there are no info about that in this issue, no any info in main branch and no any migration plan into new alpha. So, i think, my hacky solution may be helpfull for folks that sticks to stable 11.17.0