So, I have a very very simple application that is supposed to handle downloading certain files and updating them into a certain directory. The application has a simple interface with a Status Bar that should tell the user how the download process is going. So far, so good.
I have a few implementations of classes that extend the javafx.concurrent.Task class. Those classes handle the downloading and saving of files for each system, taking care of the particularities for each one. The download process itself (which actually comes from a remote database) is the same for everyone, so, it's implemented in another class which is called FileDownloader as it is, naturally, supposed to be reusable.
So, we have an UpdateTask class with a call() method that, in turn, calls some method in the FileDownloader class.
Trouble is: I would need to bind a progressProperty to the bar, but only UpdateTask has this property, while most of the work I want to show in the bar is done inside FileDownloader. I cannot call updateProgress from within the download method, as it isn't inside the Task class. I cannot pass the task into the method and call the update method there. I cannot make a listener on the progress property and listen for changes on the fields inside the downloader class. It seems I cannot do anything and the only real solution would be to count the work being done inside the call() method. Woe is me.
Is there a solution for this that doesn't involve me writing less reusable code? Example code as follows:
public class UpdateTask extends Task<Void> {
@Override
protected Void call() throws Exception {
//some other operations are performed before this part
FileDownloader downloader = new FileDownloader();
//The stuff I wanna count is in there :(
boolean isDownloaded = downloader.downloadSysVersion();
//some other stuff happens that depends on the completion of the download
}
Whoever shows me how to get the information from inside the downloadSysVersion method gets a free internet cookie.
Add a property in the FileDownloader
class representing the progress. For example, if you wanted to expose the number of bytes downloaded, you could do
public class FileDownloader {
private final ReadOnlyLongWrapper bytesDownloaded = new ReadOnlyLongWrapper();
public final long getBytesDownloaded() {
return bytesDownloaded.get();
}
public final ReadOnlyLongProperty bytesDownloadedProperty() {
return bytesDownloaded.getReadOnlyProperty();
}
private long totalBytes ;
public long getTotalBytes() {
return totalBytes ;
}
// other code as before...
public boolean downloadSysVersion() {
// code as before, but periodically call
bytesDownloaded.set(...);
// ...
}
}
Now you do
@Override
protected Void call() throws Exception {
FileDownloader downloader = new FileDownloader();
downloader.bytesDownloadedProperty().addListener((obs, oldValue, newValue) ->
updateProgress(newValue.longValue(), downloader.getTotalBytes()));
boolean isDownloaded = downloader.downloadSysVersion();
// ...
}
If you want to make the FileDownloader
class independent of the entire JavaFX API (including the property classes), you can use a LongConsumer
instead:
public class FileDownloader {
private LongConsumer progressUpdate ;
public void setProgressUpdate(LongConsumer progressUpdate) {
this.progressUpdate = progressUpdate ;
}
private long totalBytes ;
public long getTotalBytes() {
return totalBytes ;
}
public boolean downloadSysVersion() {
// periodically do
if (progressUpdate != null) {
progressUpdate.accept(...); // pass in number of bytes downloaded
}
// ...
}
}
and in this case your task looks like
@Override
protected Void call() throws Exception {
FileDownloader downloader = new FileDownloader();
downloader.setProgressUpdate(bytesDownloaded ->
updateProgress(bytesDownloaded, downloader.getTotalBytes()));
boolean isDownloaded = downloader.downloadSysVersion();
// ...
}
With either of these setups, you can then just bind the progress bar's progressProperty
to the task's progressProperty
as usual.
In your UpdateTask class, add:
@Override
public void updateProgress(double workDone, double max) {
super.updateProgress(workDone, max);
}
You'll need to pass the UpdateTask instance to your FileDownlader instance instance somehow, probably thru constructor, or however.
Then you should be able to call updateProgress.