FileDownloader without button

Hello!

I’m here to tell that i was also having trouble with the fact that the FileDownloader is called before the button click. The below code, using button ClickListener event and setFileDownloadResource from FileDownloader worked fine for me:

final FileDownloader fileDownloader = new FileDownloader(resource);
fileDownloader.setOverrideContentType(false);
downloadButton.addClickListener(listener -> {
	StreamSource newSource = getStreamSource(simulationStockComboBoxFilter, defaultValue); //my custom StreamSource getter
	StreamResource newResource = new StreamResource(newSource,
			fileNameBeginning + formatter.format(LocalDateTime.now()) + ".xls");
	newResource.setMIMEType("application/vnd.ms-excel");
	fileDownloader.setFileDownloadResource(newResource);
});
fileDownloader.extend(downloadButton);

IMPORTANT: One thing that is mandatory for this to work is to use different filenames for every downloaded file, so that we don’t use the cache.

For me, FileDownloadWrapper.setFileName is not working:

		Button button = ComponentHelper.getPrimaryButton("XLS Report", false);
		StreamResource streamResource = new StreamResource("Report.xlsx", () -> {
			...
		});

		FileDownloadWrapper buttonWrapper = new FileDownloadWrapper(streamResource);
		buttonWrapper.wrapComponent(button);
		buttonWrapper.setFileName("test.xlsx");

After clicking on XLS Report, Report.xlsx is downloaded.

Vaadin 14.0.15
FileDownloadWrapper 3.0.1

any ideas?

— edit —
looks like I have to set new StreamResource with different name: https://vaadin.com/directory/component/file-download-wrapper/discussions

Gill Roger:

How to use a button to generate a file and download it in the same action event

The solution from above did not work for me (the listener was triggered after download started).
To solve the problem I needed to create DynamicFileResource with a reflection hack (because Vaadin team decided to make the method FileResource.setSourceFile(File sourceFile) private).


	@SuppressWarnings("serial")
	public static class DynamicFileResource extends FileResource {

		protected Callable<String> processor;

		/** provide the source file using callback */
		public DynamicFileResource(Callable<String> processor) {
			super(new File("."));
			this.processor = processor;
		}

		@Override
		public DownloadStream getStream() {
			try {
				// process and set the resulted file before returning stream
				String filePath = processor.call();
				setSourceFile(new File(filePath));
			} catch (Exception e) {
				throw new IllegalStateException("Unable to generate file resource", e);
			}
			return super.getStream();
		}

		// set sourceFile (maybe Vaadin team should make the method setSourceFile(File sourceFile) public..)
		public void setSourceFile(File sourceFile) {
			// access private member: FileResource.sourceFile	
			try {
				Field f = FileResource.class.getDeclaredField("sourceFile");
				f.setAccessible(true); // force accessible
				f.set(this, sourceFile);
			} catch (Exception e) {
				throw new IllegalStateException("Unable to set sourceFile", e);
			}
		}

	}
	
	
	public enum EXPORT_TYPE {
		CSV, EXCEL, PDF
	}

	/** generated and download report */
	private void attachReportExporter(Button button, final EXPORT_TYPE exportType) {
		FileDownloader fileDownloader = new FileDownloader(new DynamicFileResource(() -> generateReport(exportType)));		
		fileDownloader.extend(button);
	}
	
	private String generateReport(EXPORT_TYPE exportType) {
        // generate the report and return the path to resulted file
        ...
        return filePath;
	}
	
	
	// sample usage attachExportReport()
	private void createLayout() {
		btnExportCsv = new Button("Export Csv");		
		attachReportExporter(btnExportCsv, EXPORT_TYPE.CSV);
		
		btnExportExcel = new Button("Export Excel");
		attachReportExporter(btnExportExcel, EXPORT_TYPE.EXCEL);
		
		btnExportPdf = new Button("Export PDF");
		attachReportExporter(btnExportPdf, EXPORT_TYPE.PDF);
	}

Thank you very much Gill, the class DynamicFileResource solve my problem