Home > Software engineering >  How to download multiple files from R/Shiny app?
How to download multiple files from R/Shiny app?

Time:12-17

There are a number of different Q/A's regarding this topic on SO, but none that I have been able to find that fit my use-case. I am also very surprised that RStudio / the Shiny developers themselves have not come out with some documentation on how to do this. Regardless, take this example application:

library(shiny)
library(glue)
library(tidyverse)

# Define UI for application 
ui <- fluidPage(
    # Application title
    titlePanel("Test Multi-File Download"),
    p("I hope this works!"),
    downloadButton(
        outputId = "download_btn",
        label = "Download",
        icon = icon("file-download")
    )
)

# Define server logic 
server <- function(input, output) {
    
    #datasets stored in reactiveValues list
    to_download <- reactiveValues(dataset1 = iris, dataset2 = airquality, dataset3 = mtcars, dataset4 = NULL)
    blahblah <- iris
    
    output$download_btn <- downloadHandler(
        filename = function(){
            paste("my_data_", Sys.Date(), ".csv", sep = "")
        },
        content = function(file){
            
           #works
           #readr::write_csv(blahblah, file)
 
                
            #Attempt 1
           #  #create some temp directory
           #  temp_directory <- tempdir()
           # browser()
           #   reactiveValuesToList(to_download) %>%
           #       #x is data, y is name
           #      imap(function(x,y){
           #          #browser()
           #          #check if data is not null
           #          if(!is.null(x)){
           #              #create file name based on name of dataset
           #              file_name <- glue("{y}_data.csv")
           #              #write file to temp directory
           #              readr::write_csv(x, file_name)
           #          }
           #      })
            
            # zip::zip(
            #     zipfile = file,
            #     files = ls(temp_directory),
            #     root = temp_directory
            # )

        }
        
    )

    
}

# Run the application 
shinyApp(ui = ui, server = server)

I have some datasets that are stored in a reactiveValues list, and I would like the user to be able to download them all. Ideally, I'd like for them just to be able to download multiple files all at once, rather than having to zip them up, and then download a .zip file. Another option I would be okay with is to add each dataset to an Excel sheet, then download the multi-sheet Excel file. My general thought process (on the former) is as follows:

  1. Download button gets pressed
  2. create some temporary directory
  3. write (the not NULL) datasets contained in to_download reactiveValues list to this directory
  4. zip the temp directory and download

I feel like I am very close, however I have not been able to successfully get this work yet. Any ideas?

Edit 1: I am aware of the proposed answer here, but would like to avoid using setwd() because I believe it is bad practice to mess with working directories from within a Shiny application.

CodePudding user response:

A few things edited and it's working:

  • using dir instead of ls inside the zip::zip call to show the contents of the temp directory (ls lists R environment rather than directory contents)
  • as a further suggestion: making a new, unique folder inside tempdir() to ensure only relevant files are added.
library(shiny)
library(glue)
library(tidyverse)

# Define UI for application 
ui <- fluidPage(
  # Application title
  titlePanel("Test Multi-File Download"),
  p("I hope this works!"),
  downloadButton(
    outputId = "download_btn",
    label = "Download",
    icon = icon("file-download")
  )
)

# Define server logic 
server <- function(input, output) {
  
  #datasets stored in reactiveValues list
  to_download <- reactiveValues(dataset1 = iris, dataset2 = airquality, dataset3 = mtcars, dataset4 = NULL)
  blahblah <- iris
  
  output$download_btn <- downloadHandler(
    filename = function(){
      paste("my_data_", Sys.Date(), ".zip", sep = "")
    },
    content = function(file){
      
       temp_directory <- file.path(tempdir(), as.integer(Sys.time()))
       dir.create(temp_directory)
       
        reactiveValuesToList(to_download) %>%
           imap(function(x,y){
               if(!is.null(x)){
                   file_name <- glue("{y}_data.csv")
                   readr::write_csv(x, file.path(temp_directory, file_name))
               }
           })
        
      
      zip::zip(
          zipfile = file,
          files = dir(temp_directory),
          root = temp_directory
      )
      
      

    },
    contentType = "application/zip"
    
  )
  
  
}

shinyApp(ui = ui, server = server)

In my own Shiny app I had used a multi-worksheet approach as you suggested above. An alternative setup which works to produce a multi-sheet xlsx workbook using openxlsx could be:

...

output$download_btn <- downloadHandler(
    filename = function(){
      paste("my_data_", Sys.Date(), ".xlsx", sep = "")
    },
    content = function(file){
      
       wb <- createWorkbook()
       
        reactiveValuesToList(to_download) %>%
           imap(function(x,y){
               if(!is.null(x)){
                 addWorksheet(wb, sheetName = y)
                 writeData(wb, x, sheet = y)
               }
           })
        
        saveWorkbook(wb, file = file)
    },
    contentType = "file/xlsx"
    
  )

...

Created on 2021-12-16 by the reprex package (v2.0.1)

  • Related