Autumn
Autumn

Reputation: 3746

Javafx Task<ObserveableList>.updateValue only fires first change event

I'd like to observe a task's valueProperty, and take action when it's changed by updateValue(). The change event only seems to get fired on the first update though.

Oracle's getValue document has a section implying that it's kosher to repeatedly call UpdateValue to return partial results. Perhaps I'm not understanding what they mean by "updates are coalesced".

Minimum Example

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.stage.Stage;

public class Main extends Application {
    MyTask task = new MyTask();
    ListView<String> listView = new ListView<>();

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(listView));
        primaryStage.show();

        Thread taskThread = new Thread(task);
        taskThread.start();

        task.valueProperty().addListener( (ob,old,nw) -> listView.getItems().addAll(nw) ); // Only fires once.
        //task.lastString.addListener( iv -> listView.getItems().addAll(task.lastString.getValue()) ); // Fires every add
    }

    public static void main(String[] args) { launch(args); }
}

class MyTask extends Task<ObservableList<String>> {
    ObservableList<String> list = FXCollections.observableArrayList();
    public ReadOnlyObjectWrapper<String> lastString = new ReadOnlyObjectWrapper<>(new String());
    Integer maxWork = 4;

    @Override
    protected ObservableList<String> call() throws Exception {
        for( Integer stringNo=0; stringNo<maxWork; stringNo++) {
            Thread.sleep(500);
            String addMe = new String("Thread string " + stringNo);
            list.add(addMe);
            updateProgress(stringNo, maxWork);
            updateValue(list);   // Only fires one change event
            Platform.runLater( () -> {
                lastString.setValue(addMe); // Works as expected.
                 } );
        }
        return list;
    }
}

Upvotes: 3

Views: 1784

Answers (2)

Autumn
Autumn

Reputation: 3746

For anyone coming along later, here's the code, modified to provide direct access to the listView itself.

package demo;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.stage.Stage;

import java.util.List;

public class Main extends Application {
    MyTask task = new MyTask();
    ListView<String> listView = new ListView<>();

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(listView));
        primaryStage.show();

        task.lv = listView;
        Thread taskThread = new Thread(task);
        taskThread.start();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

class MyTask extends Task<ObservableList<String>> {
    ObservableList<String> list = FXCollections.observableArrayList();
    public ReadOnlyObjectWrapper<String> lastString = new ReadOnlyObjectWrapper<>(new String());
    Integer maxWork = 4;
    ListView lv;

    @Override
    protected ObservableList<String> call() throws Exception {
        updateValue(list); // make sure that the task's valueproperty has a value.
        Platform.runLater(() -> addMyListener());


        for (Integer stringNo = 0; stringNo < maxWork; stringNo++) {
            Thread.sleep(500);
            String addMe = new String("Thread string " + stringNo);
            list.add(addMe);
            updateProgress(stringNo, maxWork);
            updateValue(list);   // Only fires one change event
            Platform.runLater(() -> {
                lastString.setValue(addMe); // Works as expected.
            });
        }
        return list;
    }

    void addMyListener() {
        ObservableList<String> obl = valueProperty().getValue();
        obl.addListener(new ListChangeListener<String>() {
            @Override
            public void onChanged(Change<? extends String> c) {
                while (c.next()) {
                    List added = c.getAddedSubList();
                    Platform.runLater(() -> lv.getItems().addAll(added));
                }
            }
        });
    }


}

Upvotes: 3

James_D
James_D

Reputation: 209340

The value of the task only changes once. Initially it is null. On the first iteration it changes so the value==list and on every subsequent iteration, value==list (i.e. there are no further changes).

You probably want to:

  1. Update the list on the FX Application thread, by calling Platform.runLater(() -> list.add(addMe));
  2. Arrange to observe the list itself, instead of the task's value property.

Upvotes: 4

Related Questions