1

The following setup is what I have and it describes a nesting of UI elements into a graph with growing complexity:

public class BuildingListView extends ListView<Building> {
  ...
  //herein defines a BuildingCellFactory for this ListView's cells
  ...
}

public class BuildingCell extends ListCell<Building> {
  GridPane customGridPane= new CustomGridPane();
  WorkerTable workerTable = new WorkerTable();
  ...
  @Override
  protected void updateItem(final Building building, final boolean isEmpty) {
    ...
    this.workerTable.setItems(building.getWorkers());
  }
}

public class WorkerTable extends TableView<Worker> {
  ...
  //with its own cell factory with nested UI components, tables, lists, accordions 
}

I find that when the top-level BuildingListView changes sort order, or is scrolled, various cells at all levels don't actually seem to be re-used and I have what seems to be LOTS and LOTS of UI components at all levels of the graph being allocated and de-allocated by various UI interactions. It seems extremely wasteful.

During a given execution I may have 10 buildings with 10-30 workers each and maybe 40 UI elements per worker, which if it were all just created and kept in memory would translate to roughly 6,000 to 16,000 total UI components. However, scrolling around and using the software over the course of half an hour actually results in millions of allocations and de-allocations.

It makes me want to create caches of these instances and re-use them manually, but it seems like JavaFX should be doing that for me under the hood (but doesn't seem to be).

Am I doing something wrong? Is there another way to structure my UI component construction work outside of these "updateItem" methods so that those are strictly for setting values in the UI components rather than instantiating entire graphs of objects as the UI is used?

I've tried the above setup, and my expectation is that there would be a maximum of maybe 20 instances of BuildingCell ever instantiated, but in practice there are many more and the instantiations increase over time.

DGPT10
  • 23
  • 2

1 Answers1

4

This is a generic answer to your question title about UI node instantiation for ListView and TableView cells, with additional information on handling the model item data for the item list backing the UI. It is not intended as a specific answer for your situation.

UI Nodes

Node instantiation should occur one time within the cell.

Either:

  • Create the required nodes up front in the cell constructor
  • OR perform a lazy instantiation once per node in updateItem when needed.

In subsequent updateItem calls do not create a new node, instead reuse the node(s) you already created.

If you are creating a new node in a constructor or updateItem call and not storing and reusing it in the next updateItem call then you are doing it wrong (which is impossible to tell from the code provided).

Non-UI Items

Items (non-UI backing model data) to be displayed in the nodes represented in the cells will be retrieved from the table or list view's item backing list as needed. This could be up-front before the view is displayed and/or later on if the item list needs to change during application operation. When updates to the item list occur, the corresponding cells will be updated, as the item list is observable. When updates occur to fields of an item, an extractor can be used to trigger an updateItem call for the cell.

Much of the time, you don't need to worry about caching item data. However, if the amount of data required for each item is large, you can consider caching the item data. Examples where you might consider caching:

Alternatives to complex table cells

If you find that you have large amounts of complex data displayed in a single cell or complex UI interactions and editing within a cell, consider if you have the appropriate design for your application. Alternative designs to consider are:

FAQ

for these nested UI components, where does DB connectivity typically go? For example, I have a table of workers, but as that table is populated each cell needs additional data from the DB. It seems inappropriate to put the DB call inside the "updateItem" implementation even though that's where the cell "knows" the keys for the DB query it needs to run. That "update" feels like it should only be populating UI components, not fetching data.

Yes, you have to be very careful about running database calls inside of updateItem.

It is best to fetch the required list data upfront before displaying the table.

If that is too expensive, then consider some of these options (independently or together):

  • Alternate UI options mentioned in the previous section.
  • Only fetching additional data on user interaction.
    • e.g. when a cell is selected or when the user clicks on a link or button in the cell or external to the table to expand the cell data.
  • Consider loading data asynchronously in a task:
    • See Task documentation for returning partial results to populate your list incrementally (e.g. a JavaFX version of infinite scroll).
    • Or run a Task in the background to retrieve additional info for displayed cells so the detail for the displayed cells is populated and added over time.
  • Complex nodes can also be created or manipulated in a Task as long as those nodes are not in an actively displayed scene graph at that time.

There are many performance optimization options. Which to choose depends on design preferences, skill level, and application requirements. Some options may be difficult to implement well, especially async tasks triggered in updateItem, you would want to make sure that you have exhausted some other options before you try something like that.

And don't forget to follow the basic rule of only creating nodes for a cell once, then reuse them when the item for the cell gets updated. Also, as remarked in comments by trashgod, with performance related analysis, benchmark and profile.

jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • Thanks for your help, the explanation, and links you provided. Maybe this should be in a separate question, but for these nested UI components, where does DB connectivity typically go? For example, I have a table of workers, but as that table is populated each cell needs additional data from the DB. It seems inappropriate to put the DB call inside the "updateItem" implementation even though that's where the cell "knows" the keys for the DB query it needs to run. That "update" feels like it should only be populating UI components, not fetching data. – DGPT10 Jun 23 '23 at 13:58
  • Added FAQ section. – jewelsea Jun 23 '23 at 17:05