3

I have a table view that lists user's friends and I need to update it every 5 seconds with data that I retrieve from database.

This is the code I use:

Main.java
   private List<Friend> userFriends;

fx controller:

    ObservableList<FriendWrapper> friendList = FXCollections.observableList(
    new ArrayList<FriendWrapper>());

private void updateFriendList() {
    new Thread(new Runnable() {
        public void run() {
            while (Params.loggedUser != null) {
                Main.setUserFriends(Params.dao.listUserFriends(Params.loggedUser));
                friendList.clear();
                for (Friend friend : Main.getUserFriends()) {
                    friendList.add(new FriendWrapper(friend.getFriendName(), friend.getOnline(), friend.getFriendId(), friend.getWelcomeMessage()));
                }
                Params.dao.updateOnlineStatus(Params.loggedUser, 3);
                try {
                    Thread.sleep(1000 * 5); 
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }, "updateFriendList").start();
}

Friend is database model. FriendWrapper is object used for table rows.

however I get IllegalStateException: Not on FX application thread on line friendList.clear();

How can I change the items of TableView from a thread running in the background?

Asalas77
  • 612
  • 4
  • 15
  • 26

2 Answers2

10

Instead of a quick Platform.runLater() hack, you should probably make use of the Task class:

protected class LoadFriendsTask extends Task<List<FriendWrapper>>
{

    @Override
    protected List<FriendWrapper> call() throws Exception {

        List<Friend> database = new ArrayList<>(); //TODO fetch from DB
        List<FriendWrapper> result = new ArrayList<>();
        //TODO fill from database in result
        return result;
    }

    @Override
    protected void succeeded() {
        getTableView().getItems().setAll(getValue());
    }

}

You can launch this one as a Thread, for example:

new Thread(new LoadFriendsTask()).start()

For further reference:

Community
  • 1
  • 1
eckig
  • 10,964
  • 4
  • 38
  • 52
  • It would probably be better, in the override of the `call` method, to define a better exception than `Exception`. Just as a quick recall, `RuntimeException` extends `Exception`, therefore catching `Exception` means you catch all unchecked exceptions as well. – fge Feb 11 '15 at 12:42
  • @fge It is the default signature of the `Task`s `call()` method. – eckig Feb 11 '15 at 12:44
  • Which doesn't mean that you can't specify a better exception, and in fact you _should_. `Closeable` extends `AutoCloseable`, for instance, and it specifies that the exception thrown is `IOException`, not `Exception`. Or you can specify no exception at all. – fge Feb 11 '15 at 12:45
  • @fge You are right. But catching everything also supports the `Task`s `onFailed` hook.. Depends on the use case ;) – eckig Feb 11 '15 at 12:48
  • @eckig how can I launch this in a loop with fixed time intervals? – Asalas77 Feb 11 '15 at 15:34
  • @Asalas77 have a look at this beauty: https://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/ScheduledService.html – eckig Feb 11 '15 at 16:33
  • @eckig i am using java 7 so I can't use fx 8.0. Any other options? – Asalas77 Feb 11 '15 at 16:43
  • @Asalas77 http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledExecutorService.html should work or a `TimeLine` (http://stackoverflow.com/questions/26916640/javafx-not-on-fx-application-thread-when-using-timer/26916766#26916766). In both cases: Remember to always create a new `Task` object, as they are not reusable. – eckig Feb 11 '15 at 16:46
3

Use this...

Platform.runLater(new Runnable() {
    @Override
    public void run() {

    }
});
Vikas Tiwari
  • 367
  • 1
  • 11
  • 1
    http://docs.oracle.com/javafx/2/api/javafx/application/Platform.html#runLater%28java.lang.Runnable%29 – rns Feb 11 '15 at 11:56