This discussion is archived
5 Replies Latest reply: Jan 16, 2013 7:29 AM by 966626 RSS

looking for best practice on showing data inside a TableView

966626 Newbie
Currently Being Moderated
I have an app, that can retrieve one ore more than 300,000 rows as the result of a query and i am displaying them into a TableView.. i am using a mechanism to bring the data "on parts", when i do the query an object with the ids of the rows is set into memory and a "queryResultKey" is passed in order to start asking for the complete set of data.
The controlers send the queryResultKey telling the DAO that it has already N resultas, so the DAO will return X registers (from N+1 to N+J) or less if the total number of registers is reached. At this point the registers are added to the items of the table view using a addAll to the list binded to the table itemsProperty.
it works fine for the first thounsands registers but suddenly a Null Pointer Exception is raised:
ene 15, 2013 12:56:40 PM mx.gob.scjn.iusjfx.presentacion.tesis.TablaResultadosController$5 call
SEVERE: null
java.lang.NullPointerException
     at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:291)
     at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:48)
     at com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList.callObservers(ReadOnlyUnbackedObservableList.java:74)
     at javafx.scene.control.TableView$TableViewArrayListSelectionModel$3.onChanged(TableView.java:1725)
     at com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:134)
     at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:48)
     at com.sun.javafx.collections.ObservableListWrapper.callObservers(ObservableListWrapper.java:97)
     at com.sun.javafx.collections.ObservableListWrapper.clear(ObservableListWrapper.java:184)
     at javafx.scene.control.TableView$TableViewArrayListSelectionModel.quietClearSelection(TableView.java:2154)
     at javafx.scene.control.TableView$TableViewArrayListSelectionModel.updateSelection(TableView.java:1902)
     at javafx.scene.control.TableView$TableViewArrayListSelectionModel.access$2600(TableView.java:1681)
     at javafx.scene.control.TableView$TableViewArrayListSelectionModel$8.onChanged(TableView.java:1802)
     at com.sun.javafx.scene.control.WeakListChangeListener.onChanged(WeakListChangeListener.java:71)
     at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:291)
     at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:48)
     at com.sun.javafx.collections.ObservableListWrapper.callObservers(ObservableListWrapper.java:97)
     at com.sun.javafx.collections.ObservableListWrapper.addAll(ObservableListWrapper.java:171)
     at com.sun.javafx.collections.ObservableListWrapper.addAll(ObservableListWrapper.java:160)
     at javafx.beans.binding.ListExpression.addAll(ListExpression.java:280)
     at mx.gob.scjn.iusjfx.presentacion.tesis.TablaResultadosController$5.call(TablaResultadosController.java:433)
     at mx.gob.scjn.iusjfx.presentacion.tesis.TablaResultadosController$5.call(TablaResultadosController.java:427)
     at javafx.concurrent.Task$TaskCallable.call(Task.java:1259)
     at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
     at java.util.concurrent.FutureTask.run(FutureTask.java:166)
     at java.lang.Thread.run(Thread.java:722)
This exception ocurrs inside the Thread that is filling the table:
task = new Task<Integer>() {
            @Override
            protected Integer call() throws Exception {
                while (listaTesis.size() < pag.getLargo()) {
                    List<TesisTO> tesisParaincrustar = fac.getTesisParaLista(pag.getId(), listaTesis.size());
                    try {
                        listaTesis.addAll(tesisParaincrustar);
                    } catch (Exception exc) {
                        Logger.getLogger(TablaResultadosController.class.getName()).log(Level.SEVERE, null, exc);
                    }
                    tesisActuales.addAll(tesisParaincrustar);
                    updateProgress(listaTesis.size(), pag.getLargo());
                }

                return new Integer(100);
            }

            @Override
            protected void succeeded() {
                status.getProgreso().setVisible(false);
                preparaFiltros();
                llenandoTabla = false;
                tblResultados.getSelectionModel().select(0);
            }
        };
        status.getProgreso().progressProperty().bind(task.progressProperty());
        new Thread((Runnable) task).start();
So, what can be another strategy to simulate (or have) that all the register values are in memory and "grab just the needed ones" to display them in the TableView???
Any idea will be helpfull.
  • 1. Re: looking for best practice on showing data inside a TableView
    966626 Newbie
    Currently Being Moderated
    After several tests the problems starts to show when a tableView.getSelectionModel().selectLast() (or first, or next or previous) is used and the the new rows are being inserted!!!!
    Any idea why and how to prevent it?
    Greetings
  • 2. Re: looking for best practice on showing data inside a TableView
    James_D Guru
    Currently Being Moderated
    Is either listaTesis or tesisActuales a list bound to the TableView (or to any other control)? If so, you are trying to update those from an application thread (i.e. not the FX Application Thread). This can cause problems (though I don't know if it's causing the null pointer exception you're seeing).

    Which is the line that's actually throwing the null pointer exception (i.e. which is line 433 in TablaResultadosController)?
  • 3. Re: looking for best practice on showing data inside a TableView
    966626 Newbie
    Currently Being Moderated
    The line throwing the error is:
    try {
                            listaTesis.addAll(tesisParaincrustar);
                        } catch (Exception exc) {
                            Logger.getLogger(TablaResultadosController.class.getName()).log(Level.SEVERE, null, exc);
                        }
    and is not inside the FX App Thread.. but how to update then the list without "freezing" the interface, that's the main reason the update is in a thread that is different, what should be the best practice here???
  • 4. Re: looking for best practice on showing data inside a TableView
    James_D Guru
    Currently Being Moderated
    The first thing I would do here is to check that
    fac.getTesisParaLista(pag.getId(), listaTesis.size())
    cannot return null under any circumstances. If this is returning null, then that is the source of your exception. In this case you should add the appropriate guard to your code:
    if (tesisParaincrustar != null) {
                        try {
                            listaTesis.addAll(tesisParaincrustar);
                        } catch (Exception exc) {
                            Logger.getLogger(TablaResultadosController.class.getName()).log(Level.SEVERE, null, exc);
                        }
                        tesisActuales.addAll(tesisParaincrustar);
    }
    For the threading issue, you can schedule anything that changes the UI to run on the FX Application Thread using Platform.runLater(...). Note that any data you pass into that should not be modified elsewhere. So you can do something like:
    Platform.runLater(new Runnable() {
      @Override
      public void run() {
                        try {
                            listaTesis.addAll(tesisParaincrustar);
                        } catch (Exception exc) {
                            Logger.getLogger(TablaResultadosController.class.getName()).log(Level.SEVERE, null, exc);
                        }
                        tesisActuales.addAll(tesisParaincrustar);
      }
    });
    You'll need to add the 'final' keyword to the declaration of tesisParaincrustar to get this to compile.

    This is fine, assuming that the call to fac.getTesisParaLista(...) returns a new list instance each time (i.e. it doesn't modify an existing list).

    You will be left with one remaining problem: the condition in your while(...) loop references listaTesis.size(). Since this list is now being updated on a thread different to the thread managing the while(...) loop. This can (and probably will) cause the loop to terminate at the wrong time. Can you adjust the while(...) condition so it doesn't refer to this list (for example, calculate ahead of time how many things need to be read, pass that number to your Task implementation, and use that in the while(...) condition.)

    There are effectively two rules for multithreading in JavaFX:
    1. Don't access the UI outside of the FX Application Thread. This includes data structures which are bound to the UI. I'm assuming at some point you've called tableView.setItems(listaTesis), so listaTesis should be considered part of the UI.
    2. Don't perform long-running tasks on the FX Application Thread.

    In attempting to conform to rule 2, you've violated rule 1.

    Doing incremental updates to a displayed list in a background thread is one of the trickiest uses of Task. Read through the API documentation for Task ( http://docs.oracle.com/javafx/2/api/javafx/concurrent/Task.html ): the "PartialResultsTask" example under "A Task which returns partial results" is an example of what you're trying to do.
  • 5. Re: looking for best practice on showing data inside a TableView
    966626 Newbie
    Currently Being Moderated
    Since tesisActuales is "a safe copy" of listaTesis i can use it for the while, and since it is not used on the JavaFX thread until all the rows are "downloaded" it can be modified "safely" on the non-JavaFX thread, so you make my day.. It just worked fine... thanks for the help.

Legend

  • Correct Answers - 10 points
  • Helpful Answers - 5 points