Hi,
I’m trying to implement a button that triggers a file download. I’m not using an anchor because that would require me to pre-register the resource, but I want to avoid preloading the data.
Based on an older forum post (Download link or button - #8 by Marco36) I have something like this in my button click listener:
var inputStream = ...; // secret code for getting the input stream.
final StreamResource resource = new StreamResource("myfile.zip", () -> {
try {
return inputStream;
} catch (IOException e) {
throw new RuntimeException(e);
}
});
// Register the resource in Vaadin resource registry, so that it can be downloaded.
StreamRegistration registration = VaadinSession.getCurrent().getResourceRegistry().registerResource(resource);
// Open a new tab, with the URL to resource, to initiate the download.
UI.getCurrent().getPage().open(registration.getResourceUri().toString());
In the older thread it was mentioned that opening in a new tab prevents the UI instance being destroyed. However, the customer doesn’t like the blinking caused due to another tab opening. Is there a better way in the year 2024?
I made this a long time ago. Maybe it helps. (However, it utilizies an anchor internally).
The only current known downside is, that the component needs to stay attached until the download starts. So this could need some overhauling, if it shall support “view switchting” and still start the download.
Yep, setting the location for current page (UI.getCurrent().getPage().setLocation(registration.getResourceUri());) seems to still kill (or make it unresponsive) the UI.
I appreciate all the suggestions for addons, but customer is hesitant of adding addons to the project, due to increased migration complexity, especially for this fairly “simple” functionality. Though I know Viritin is pretty much a first-party addon, so I’ll give it another push.
At the same time I thought there would be a cookbook example for this, and there is, but it’s based on Viritin addon
yes and no.
You need to have the filename ready. But the file data is only be loaded when the Steam is called. The InputStreamFactory gets only called when the file is request by the browser (clicking on the Anchor) and not when the StreamResource is created.
// move the secret code inside the InputStreamFactory
final StreamResource resource = new StreamResource("myfile.zip", () -> {
try {
return getInputStreamFromSuperSecretBackend("myfile.zip");
} catch (IOException e) {
throw new RuntimeException(e);
}
});
Let’s assume you have a Select or a CRUD layout where you select your file. In the value change listener you can setup the resource.
record FileInfo(String filename, String backendId) {}
Backend secretBackend;
Anchor anchor = new Anchor();
anchor.setText("Select file to download");
Select<FileInfo> select = new Select<FileInfo>("FileSelect");
select.setItems(secretBackend.getFileInfos())
select.addValueChangeListener(vcEvent -> {
FileInfo info = vcEvent.getValue();
anchor.setText("Download file: " + info.name());
// called when file is selected.
SteamResource sr = new StreamResource(info.name(), () -> {
// called when anchor is clicked
return secretBackend.getSteamById(info.backendId());
}
anchro.setSrc(sr);
});