How do I bind the running property of one Service to the same property of another Service that it (re)starts? The first service must be considered running as long as the "nested" service is. In my simple demo, there's only one nested service, yet there could be more.
The problematic part is service.runningProperty() does not return a javafx.beans.property.Property with a bind() method. It instead returns a ReadOnlyBooleanProperty that does not expose such a method.
I could override javafx.concurrent.Service#runningProperty in a subclass to return super.runningProperty().or(nestedRunningProperty()), but the method is final, could not be overridden.
I am hesitant to declare a separate property in my Service subclass (say, effectiveRunningProperty()). I don't want the service's clients to even know whether it has nested services. I am fine with declaring myService.addAndWire(anotherService), on the other hand, if necessary (may be called by another client beforehand, not the one requesting/ binding to the running property).
What is the best way to achieve my goal?
Here's a demo that does not achieve it. Its idea is to keep the "Start" button disabled as long as the service, in its entirety, is running.
import javafx.application.Application;
import javafx.concurrent.Service;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.time.Duration;
import java.util.function.Function;
public class FXServiceDemo extends Application {
@Override
public void start(Stage primaryStage) {
primaryStage.setScene(createScene());
primaryStage.setTitle("Service Demo");
primaryStage.show();
}
private Scene createScene() {
VBox root = createRoot();
Scene scene = new Scene(root);
return scene;
}
private VBox createRoot() {
VBox root = new VBox();
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(5));
root.getChildren().add(createStartButton());
return root;
}
private Button createStartButton() {
Service outerService = createOuterService(); // inner Service is not create in this scope
Button button = new Button();
button.textProperty().set("Start");
button.disableProperty().bind(outerService.runningProperty()); // button doesn't know there could be nested services
Function toWord = disable -> disable ? "DISABLED" : "ENABLED";
button.disableProperty().addListener((ob, ov, nv) -> System.out.printf("Button was %s, but now is %s \n", toWord.apply(ov), toWord.apply(nv)));
button.onActionProperty().setValue(e -> outerService.restart());
return button;
}
private static Service createOuterService() {
Service service = Services.from(FXServiceDemo::perform);
return service;
}
private static void perform() {
System.out.println("Doing something before launching the nested Service...");
sleep(Duration.ofSeconds(1));
System.out.println("Launching the nested Service...");
startInnerService();
}
private static void startInnerService() {
Service service = createInnerService();
service.restart();
}
private static void sleep(Duration duration) {
try {
Thread.sleep(duration.toMillis());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private static Service createInnerService() {
Service service = Services.from(FXServiceDemo::performNested);
return service;
}
private static void performNested() {
System.out.println("Doing something in the nested Service...");
sleep(Duration.ofSeconds(2));
System.out.println("Nested completed");
}
}
import javafx.concurrent.Service;
import javafx.concurrent.Task;
public class Services {
public static Service from(Runnable task) {
return new Service() {
@Override
protected Task createTask() {
return new Task() {
@Override
protected Void call() {
task.run();
return null;
}
};
}
};
}
}
Once you run it and press the button, you get this console:
Connected to the target VM, address: '127.0.0.1:30475', transport: 'socket'
Button was ENABLED, but now is DISABLED
Doing something before launching the nested Service...
Launching the nested Service...
Button was DISABLED, but now is ENABLED
Doing something in the nested Service...
Nested completed
Instead, it should be:
Connected to the target VM, address: '127.0.0.1:30475', transport: 'socket'
Button was ENABLED, but now is DISABLED
Doing something before launching the nested Service...
Launching the nested Service...
Doing something in the nested Service...
Nested completed
Button was DISABLED, but now is ENABLED
Java 8, JavaFX 8.