7

Below is my code. It might seem a bit long but actually it's a VERY simple app.

The user is supposed to upload a tiny data frame (x.csv if you are in the US or x_Europe.csv if you are in Europe). Then the user should click on the button to start calculations. And then at the end the user should be able to download the results of those calculations as a data frame.

My problem: after I upload the file, when I click on the 'do_it' action button - nothing happens. I can see it because nothing is being printed to my console. WHY? After all, my function 'main_calc' should be eventReactive to input$do_it? Why do all the calculations inside main_calc start happening ONLY after the user tries to download the results?

Important: It is important to me to keep the 'Data' function separately from main_calc.

Thank you very much!

First, generate one of these 2 files in your working directory:

# generate file 'x.csv' to read in later in the app:
write.csv(data.frame(a = 1:4, b = 2:5), "x.csv", row.names = F)  # US file
write.csv2(data.frame(a = 1:4, b = 2:5), "x_Europe.csv", row.names = F)

This is the code for the shiny app:

library(shiny)

ui <- fluidPage(
  # User should upload file x here:
  fileInput("file_x", label = h5("Upload file 'x.csv'!")),
  br(),
  actionButton("do_it", "Click Here First:"),
  br(),
  br(),
  textInput("user_filename","Save your file as:", value = "My file x"),
  downloadButton('file_down',"Save the output File:")
)

server <- function(input, output, session) {

  #----------------------------------------------------------------------
  # Function to read in either European (csv2) or American (csv) input:
  #----------------------------------------------------------------------

  ReadFile <- function(pathtofile, withheader = TRUE){

    test <- readLines(pathtofile, n = 1)  
    if (length(strsplit(test, split = ";")[[1]]) > 1) {
      print("Reading European CSV file")
      outlist <- list(myinput = read.csv2(pathtofile, header = TRUE),
                      europe.file = 1)
    } else {
      print("Reading US CSV file")
      outlist <- list(myinput = read.csv(pathtofile, header = TRUE),
                      europe.file = 0)
    }
    return(outlist)
  }

  #----------------------------------------------------------------------
  # Data-related - getting the input file
  #----------------------------------------------------------------------  

  Data <- reactive({

    print("Starting reactive function 'Data'")
    # Input file:
    infile_x <- input$file_x
    myx <- ReadFile(infile_x$datapath)$myinput

    # European file?
    europe <- ReadFile(infile_x$datapath)$europe.file

    print("Finishing reactive function 'Data'")
    return(list(data = myx, europe = europe))

  })

  #----------------------------------------------------------------------
  # Main function that should read in the input and 'calculate' stuff
  # after the users clicks on the button 'do_it' - takes about 20 sec
  #----------------------------------------------------------------------

  main_calc <- eventReactive(input$do_it, {

    req(input$file_x)

    # Reading in the input file:
    x <- Data()$data
    print("Done reading in the data inside main_calc")

    # Running useless calculations - just to kill time:

    myvector <- matrix(unlist(x), ncol = 1, nrow = 1000)
    print("Starting calculations")

    for (i in seq_len(10)) {
      set.seed(12)
      mymatr <- matrix(abs(rnorm(1000000)), nrow = 1000)
      temp <- solve(mymatr) %*% myvector
    }

    print("Finished calculations")

    # Creating a new file:
    y <- temp
    result = list(x = x, y = y)
    print("End of eventReactive function main_calc.")
    return(result)
  })   # end of main_calc

  #----------------------------------------------------------------------
  # The user should be able to save the output of main_calc as a csv file
  # using a string s/he specified for the file name:
  #----------------------------------------------------------------------

  output$file_down <- downloadHandler(
    filename = function() {
      paste0(input$user_filename, " ", Sys.Date(), ".csv") 
    },
    content = function(file) {
      print("Europe Flag is:")
      print(Data()$europe)

      if (Data()$europe == 1) {
        x_out <- main_calc()$x
        print("Dimensions of x in downloadHandler are:")
        print(dim(x_out))        
        write.csv2(x_out, 
                   file,
                   row.names = FALSE)
      } else {
        x_out <- main_calc()$x
        print("Dimensions of x in downloadHandler are:")
        print(dim(x_out))
        write.csv(x_out, 
                  file,
                  row.names = FALSE)
      }
    }
  )


}  # end of server code  

shinyApp(ui, server)
user3245256
  • 1,842
  • 4
  • 24
  • 51
  • Try adding the `main_calc()` as a first line in the `downloadHandler` to create the dependency – Pork Chop Feb 21 '18 at 16:18
  • 3
    Maybe this question will help clarify: https://stackoverflow.com/questions/33519816/shiny-what-is-the-difference-between-observeevent-and-eventreactive. `eventReactive` objects don't "run" when the event happens, they are just marked as invalidated. They aren't run till the value is actually requested from them. So since you don't use `main_calc()` till the download button is pressed, it's not run till then. You probably want `observeEvent` with a `reactiveValues` object for this type of interaction. – MrFlick Feb 21 '18 at 16:23
  • Interesting (and crazy!) - I've added main_calc(), as the first line inside downloadHandler. Indeed - the calculations ran after I clicked on the button, BUT: after the calculations were over, the file download button was super-small and when I clicked on it, everything crashed: "Warning in self$downloads$set(name, list(filename = filename, contentType = contentType, : restarting interrupted promise evaluation" and then: Error in run(timeoutMs) : Expecting a single string value: [type=character; extent=2]. – user3245256 Feb 21 '18 at 16:24
  • @MrFlick But if I use observeEvent - I can't return anything, can I? What should my downloadHandler download if I don't have some function (like main_calc) that returns objects I need to download? – user3245256 Feb 21 '18 at 16:27
  • 1
    @user3245256 Right, you can't return anything. That's why you need to update a value in a reactiveValues object that lives outside the event. See this simple example: https://gist.github.com/aagarw30/69feeeb7e813788a753b71ef8c0877eb – MrFlick Feb 21 '18 at 16:30
  • @MrFlick Thank you for a clear example. In this simple example, the reactive values is just one simple value: counter <- reactiveValues(countervalue = 0). Can I make it a list? Something like: myreactivelist <- reactiveValues(list(object1 = NULL, object2 = NULL)? And then update them one at a time? – user3245256 Feb 21 '18 at 16:34
  • See the documentation for reactiveValues: https://shiny.rstudio.com/reference/shiny/0.11/reactiveValues.html. It's already a list. Just use `reactiveValues(object1 = NULL, object2 = NULL)` (rather than a nested list). – MrFlick Feb 21 '18 at 16:36
  • Consider wrapping the findings up in an answer. For future viewers, – K. Rohde Feb 21 '18 at 16:40
  • Thank you very much! It's working. Instead of main_calc I now have " observeEvent(input$do_it, {" and right before that I have "forout_reactive <- reactiveValues()" - and inside the observeEvent I assign the results to forout_reactive: "forout_reactive$y = y". And use those reactive values in downloadHandler – user3245256 Feb 21 '18 at 16:47

2 Answers2

3

Below is the solution - based on MrFlick's suggestions:

# generate file 'x.csv' to read in later in the app:
# write.csv(data.frame(a = 1:4, b = 2:5), "x.csv", row.names = F)
# write.csv2(data.frame(a = 1:4, b = 2:5), "x_Europe.csv", row.names = F)

library(shiny)
library(shinyjs)

ui <- fluidPage(
  # User should upload file x here:
  fileInput("file_x", label = h5("Upload file 'x.csv'!")),
  br(),
  actionButton("do_it", "Click Here First:"),
  br(),
  br(),
  textInput("user_filename","Save your file as:", value = "My file x"),
  downloadButton('file_down',"Save the output File:")
)

server <- function(input, output, session) {



  #----------------------------------------------------------------------
  # Function to read in either European (csv2) or American (csv) input:
  #----------------------------------------------------------------------

  ReadFile <- function(pathtofile, withheader = TRUE){

    test <- readLines(pathtofile, n = 1)  
    if (length(strsplit(test, split = ";")[[1]]) > 1) {
      print("Reading European CSV file")
      outlist <- list(myinput = read.csv2(pathtofile, header = TRUE),
                      europe.file = 1)
    } else {
      print("Reading US CSV file")
      outlist <- list(myinput = read.csv(pathtofile, header = TRUE),
                      europe.file = 0)
    }
    return(outlist)
  }


  #----------------------------------------------------------------------
  # Data-related - getting the input file
  #----------------------------------------------------------------------  

  Data <- reactive({


    print("Starting reactive function Data")
    # Input file:
    infile_x <- input$file_x
    myx <- ReadFile(infile_x$datapath)$myinput

    # European file?
    europe <- ReadFile(infile_x$datapath)$europe.file

    print("Finishing reactive function 'Data'")
    return(list(data = myx, europe = europe))

  })

  #----------------------------------------------------------------------
  # Main function that should read in the input and 'calculate' stuff
  # after the users clicks on the button 'do_it' - takes about 20 sec
  #----------------------------------------------------------------------


  # Creating reactive Values:
  forout_reactive <- reactiveValues()

  observeEvent(input$do_it, {

    print("STARTING observeEvent")

    req(input$file_x)

    # Reading in the input file:
    x <- Data()$data
    print("Done reading in the data inside observeEvent")

    # Running useless calculations - just to kill time:

    myvector <- matrix(unlist(x), ncol = 1, nrow = 1000)
    print("Starting calculations")

    for (i in seq_len(10)) {
      set.seed(12)
      mymatr <- matrix(abs(rnorm(1000000)), nrow = 1000)
      temp <- solve(mymatr) %*% myvector
    }  # takes about 22 sec on a laptop

    print("Finished calculations")

    # Creating a new file:
    y <- temp
    forout_reactive$x = x
    forout_reactive$y = y
    print("End of observeEvent")
  })   # end of main_calc

  #----------------------------------------------------------------------
  # The user should be able to save the output of main_calc as a csv file
  # using a string s/he specified for the file name:
  #----------------------------------------------------------------------

  output$file_down <- downloadHandler(
    filename = function() {
      paste0(input$user_filename, " ", Sys.Date(), ".csv") 
    },
    content = function(file) {
      print("Europe Flag is:")
      print(Data()$europe)

      if (Data()$europe == 1) {
        y_out <- forout_reactive$y
        print("Dimensions of y in downloadHandler are:")
        print(dim(y_out))        
        write.csv2(y_out, 
                   file,
                   row.names = FALSE)
      } else {
        y_out <- forout_reactive$y
        print("Dimensions of y in downloadHandler are:")
        print(dim(y_out))
        write.csv(y_out, 
                  file,
                  row.names = FALSE)
      }
    }
  )


}  # end of server code  

shinyApp(ui, server)
user3245256
  • 1,842
  • 4
  • 24
  • 51
2

Here is a simple app that may help elucidate how eventReactive() works:


library(shiny)

run_data <- function() {
  paste0("Random number generated in eventReactive: ", runif(1))
}

ui <- basicPage(
  actionButton("run1", "Invalidate eventReative()"),
  actionButton("run2", "Trigger observeEvent()"),
  verbatimTextOutput("data")
)

server <- function(input, output, session) {
  
  # Initialize reactiveValues list 
  # to use inside observeEvent()
  rv <- reactiveValues(data = NULL)
  
  # This eventReactive() doesn't run when run1 button is
  # clicked. Rather, it becomes invalidated. Only when
  # data() (the reactive being returned) is actually 
  # called, does the expression inside actually run.
  # If eventReactive is not invalidated by clicking run1
  # then even if data() is called, it still won't run.
  data <- eventReactive(input$run1, {
    
    showNotification("eventReactive() triggered...")
    
    run_data()
    
  })
  
  
  # Every time run2 button is clicked,
  # this observeEvent is triggered and
  # will run. If run1 is clicked before run2,
  # thus invalidating the eventReactive
  # that produces data(), then data() will
  # contain the output of run_data() and
  # rv$data will be assigned this value.
  observeEvent(input$run2, {
    
    showNotification("observeEvent() triggered")
    
    rv$data <- data()
    
  })
  
  
  # Renders the text found in rv$data
  output$data <- renderText({
    
    rv$data
    
  })

}

shinyApp(ui, server)

In this example, run1 invalidates the eventReactive(), and run2 triggers the observeEvent() expression. In order for the data (in this case just a random number) to print, run1 must be clicked prior to run2.

The key takeaway is that the input(s) (buttons) that eventReactive() listens to don't trigger eventReactive(). Instead, they invalidate eventReactive() such that when the output from eventReactive() is required, then the expression inside eventReactive() will run. If it is not invalidated or the output is not needed, it will not run.

Giovanni Colitti
  • 1,982
  • 11
  • 24