Vaadin 10 Let user download a file with dynamically defined filename ?

I managed to implement downloading with https://vaadin.com/forum/thread/17010389
Now I need to use dynamically defined filename instead of fixed one.
Is it possible ?

Yes. You need to create a new StreamResource and set it as the href of the Anchor. Here’s a brief example:

public class MainView extends VerticalLayout {


    public MainView() {

        TextField filenameTextField = new TextField("input file name here");
        filenameTextField.setValue("default.txt");

        Anchor anchor = new Anchor(getStreamResource("default.txt", "default content"), "click me to download");
        anchor.getElement().setAttribute("download",true);
        filenameTextField.addValueChangeListener(e -> {
            anchor.setHref(getStreamResource(filenameTextField.getValue(), filenameTextField.getValue() + " contains some text"));
        });

        add(filenameTextField, anchor);
    }


    public StreamResource getStreamResource(String filename, String content) {
        return new StreamResource(filename,
                () -> new ByteArrayInputStream(content.getBytes()));
    }
}

Thank you !

I have created a class DownloadLink.

Perhaps it makes it a little bit easier

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.server.StreamResource;

public class DownloadLink extends Anchor {

	private static final long serialVersionUID = 1L;

	public DownloadLink(File file) {
		Anchor anchor = new Anchor(getStreamResource(file.getName(), file), file.getName());
		anchor.getElement().setAttribute("download", true);
		anchor.setHref(getStreamResource(file.getName(), file));
		add(anchor);
	}

	public StreamResource getStreamResource(String filename, File content) {
		return new StreamResource(filename, () -> {
			try {
				return new ByteArrayInputStream(FileUtils.readFileToByteArray(content));
			} catch (IOException e) {
				e.printStackTrace();
			}
			return null;
		});
	}
}

When you create this Class you must give the full path to the file to the constructor

        File in = new File("C:\\dev\\wcontent\\mail\\attachments\\575\\Demo.txt");
        DownloadLink downloadLink = new DownloadButton(in);
		add(downloadLink);

Instead of the line:

return new ByteArrayInputStream(FileUtils.readFileToByteArray(content));

it’s better to use:

return new BufferedInputStream(new FileInputStream(content));

Otherwise, large files require a lot of memory.

Related to this, how would one do lazy loading of the data?

I have a problem handling exception scenario reading stream.

    public StreamResource getResource(String docId, String filename) {
        return new StreamResource(filename, () -> {
            try {
                URL url = new URL("http://something.com?id=" + docId);

                HttpURLConnection conn = (HttpURLConnection) url.openConnection();

                if (conn.getResponseCode() == 200) {
                    return new BufferedInputStream(conn.getInputStream());
                }

            } catch (Exception e) {
                e.printStackTrace();
            }

           ** return null;**
        });

When null is returned, NPE triggers error handling in

        VaadinSession.getCurrent().setErrorHandler((ErrorHandler) errorEvent -> {
            ErrorNotification.show("Sistem error: ", errorEvent.getThrowable().getLocalizedMessage());
        });

but in this phase UI.getCurrent() is null and throws

 throw new IllegalStateException("UI instance is not available. It means that you are calling this method out of a normal workflow where it's always implicitely set. That may happen if you call the method from the custom thread without 'UI::access' or from tests without proper initialization.");

What is the best way to return in UI phase if stream is null?

@Nicklas, not sure if you had solved the lazy loading - if not, there is a small addon for that

https://vaadin.com/directory/component/lazy-download-button

Using anchor.href I sometimes get “No file” or “File missing” error

		Button button = ComponentHelper.getPrimaryButton("XLS Report", false);

		Anchor anchor = new Anchor(new StreamResource("Report.xlsx", () -> new ByteArrayInputStream(null)), "");
		anchor.add(button);
		anchor.getElement().setAttribute("download",true);

		button.addClickListener(event -> {
			anchor.setHref(getStreamResource4ExportExcel());
		});

		return anchor;

It seems that the latest Vaadin 14 (14.4.2 for me) broke something. After the download with the solutions above the client-side UI is unresponsive and requires a page refresh to come back (F5 on the browser).

The solution I have that worked was to open the download URI as a new window with:

UI.getCurrent().getPage().executeJs("window.open('"+registration.getResourceUri()+"', \"_blank\", \"\");");

The drawback of this is that the browser might complain about the page opening a link and block it but users should be used to this behaviour.

Filipe Leahy-Dios:
It seems that the latest Vaadin 14 (14.4.2 for me) broke something. After the download with the solutions above the client-side UI is unresponsive and requires a page refresh to come back (F5 on the browser).

The solution I have that worked was to open the download URI as a new window with:

UI.getCurrent().getPage().executeJs("window.open('"+registration.getResourceUri()+"', \"_blank\", \"\");");

The drawback of this is that the browser might complain about the page opening a link and block it but users should be used to this behaviour.

I wasn’t able to reproduce the issue with a simple 14.4.2 project. If you can provide some sample code to reproduce the breaking behavior, you should report an issue at https://github.com/vaadin/flow