But why does downloadHandler now return an empty file!?
DownloadHandler context
Table of Contents
When we start working with {shiny}, we often reach a point where it is necessary to offer the user the option to download a document generated by the application.
This document can be a PDF report, a PNG or JPEG image, or anything else.
The appropriate function for this purpose is shiny::downloadHandler()
, but beyond the (relatively) simple case shown in the documentation’s example, when it comes to using the function in a more complex scenario, it is not uncommon to encounter difficulties, even for an experienced developer.
Therefore, I would like to share with you an approach that we use at ThinkR, which handles both straightforward and more complex cases that we sometimes encounter.
TL;DR:
We will consistently use
file.copy
in thedownloadHandler
Exemple 1: Use downloadHandler
with only file.copy
Let’s look at a simple scenario together by starting with the example from the R documentation and making it more “robust”.
Before :
ui <- fluidPage(
downloadButton(outputId = "downloadData",label = "Download")
)
server <- function(input, output) {
# Our dataset
data <- mtcars
output$downloadData <- downloadHandler(
filename = function() {
paste("data-", Sys.Date(), ".csv", sep="")
},
content = function(file) {
write.csv(data, file)
}
)
}
shinyApp(ui, server)
After :
ui <- fluidPage(
downloadButton(outputId = "downloadData",label = "Download")
)
server <- function(input, output) {
local <- reactiveValues(data = mtcars,
export_file = NULL
)
observeEvent(local$data,{
out <- tempfile(fileext = ".csv")
write.csv(x = local$data,file = out)
local$export_file <- out
})
output$downloadData <- downloadHandler(
filename = function() {
paste("data-", Sys.Date(), ".csv", sep="")
},
content = function(file) {
file.copy(
from = local$export_file,
to = file
)
}
)
}
shinyApp(ui, server)
Do you see the difference? We regain control over the file that will be placed behind the button. It is created here within an observeEvent, making it easy to debug and understand its content.
Example 2: Manage file names independently
This approach makes complex scenarios more readable :
library(tidyverse)
library(shiny)
library(flextable)
ui <- fluidPage(
downloadButton(outputId = "downloadData",label = "Download")
)
server <- function(input, output) {
local <- reactiveValues(data = mtcars,
export_file = NULL
)
observeEvent(local$data,{
out <- tempfile(fileext = ".png",pattern = "table_")
# write.csv(x = local$data,file = out)
local$data %>%
flextable::flextable() %>%
flextable::save_as_image(path = out)
if (file.exists(out)){
local$export_file <- out
} else{
local$export_file <- NULL
}
})
output$downloadData <- downloadHandler(
filename = function() {
basename(local$export_file)
},
content = function(file) {
file.copy(
from = local$export_file,
to = file
)
}
)
}
shinyApp(ui, server)
It’s your turn