Search code examples
javafxconcurrencyprogress-bar

Javafx Task - update progress from a method


In a JavaFX application I wish to update a status bar according to some work logic which I've implemented in an other class.

I can't figure out how to combine my desire to pass to work logic to the method (and not to write it inside the task) and to know about the work progress percentage.

This is an example of the controller with the Task:

public class FXMLDocumentController implements Initializable {

    @FXML private Label label;    
    @FXML ProgressBar progressBar;

    @FXML
    private void handleButtonAction(ActionEvent event) {

        Service<Void> myService = new Service<Void>() {

            @Override
            protected Task<Void> createTask() {
                return new Task<Void>() {

                    @Override
                    protected Void call() throws Exception {
                        try {
                            DatabaseFunctionality.performWorkOnDb();

                            //updateProgress(1, 1);
                        } catch (InterruptedException ex) {
                            Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
                        }

                        return null;
                    }
                }; 
            }            
        };

        progressBar.progressProperty().bind(myService.progressProperty());
        myService.restart();
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
    }        
}

This is the helper class:

public class DatabaseFunctionality {

    public static void performWorkOnDb () throws InterruptedException {
        for (int i = 1; i <= 100; i++) {
            System.out.println("i=" + i);
            Thread.sleep(100);

            //Update progress
        }        
    }    
}

Thank you


Solution

  • You have a couple of options here. One is to do as Uluk suggests and expose an observable property in your DatabaseFunctionality class:

    public class DatabaseFunctionality {
    
        private final ReadOnlyDoubleWrapper progress = new ReadOnlyDoubleWrapper();
    
        public double getProgress() {
            return progressProperty().get();
        }
    
        public ReadOnlyDoubleProperty progressProperty() {
            return progress ;
        }
    
        public void performWorkOnDb() throws Exception {
            for (int i = 1; i <= 100; i++) {
                System.out.println("i=" + i);
                Thread.sleep(100);
    
                progress.set(1.0*i / 100);
            }        
        }   
    }
    

    And now in your Task, you can observe that property and update the task's progress:

    Service<Void> myService = new Service<Void>() {
    
        @Override
        protected Task<Void> createTask() {
            return new Task<Void>() {
    
                @Override
                protected Void call() throws Exception {
                    try {
                        DatabaseFunctionality dbFunc = new DatabaseFunctionality();
                        dbFunc.progressProperty().addListener((obs, oldProgress, newProgress) -> 
                            updateProgress(newProgress.doubleValue(), 1));
    
                        dbaseFunc.performWorkOnDb();
    
                    } catch (InterruptedException ex) {
                        Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
                    }
    
                    return null;
                }
            }; 
        }            
    };
    

    Another option (in case you don't want your data access object to depend on the JavaFX properties API) is to pass the data access object a callback to update the progress. A BiConsumer<Integer, Integer> would work for this:

    public class DatabaseFunctionality {
    
        private BiConsumer<Integer, Integer> progressUpdate ;
    
        public void setProgressUpdate(BiConsumer<Integer, Integer> progressUpdate) {
            this.progressUpdate = progressUpdate ;
        }
    
        public void performWorkOnDb() throws Exception {
            for (int i = 1; i <= 100; i++) {
                System.out.println("i=" + i);
                Thread.sleep(100);
    
                if (progressUpdate != null) {
                    progressUpdate.accept(i, 100);
                }
            }        
        }   
    }
    

    and then

    Service<Void> myService = new Service<Void>() {
    
        @Override
        protected Task<Void> createTask() {
            return new Task<Void>() {
    
                @Override
                protected Void call() throws Exception {
                    try {
                        DatabaseFunctionality dbFunc = new DatabaseFunctionality();
                        dbFunc.setProgressUpdate((workDone, totalWork) -> 
                            updateProgress(workDone, totalWork));
    
                        dbaseFunc.performWorkOnDb();
    
                    } catch (InterruptedException ex) {
                        Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
                    }
    
                    return null;
                }
            }; 
        }            
    };