Shiny session-global variable

I've been looking for the recommended, elegant way of having possibility to use session variable which is available in every function used within Shiny app. To show my intentions I've prepared the below code - of course it doesn't work, but I hope that is enough to show what I am looking for. It uses more complex R function - these are excerpts and are here to imitate all aspects of my Shiny app.

# app.R
rm(list=ls())
library(shiny)

source("first.R")
source("second.R")

ui <- fluidPage(
  actionButton("first", label = "First"),  
  actionButton("second", label = "Second")  
)

server <- function(input, output, session) {
  results <- 0
  observeEvent(input$first, {
    first_parse()
  })  
  observeEvent(input$second, {
    second_summary()
  })
}

shinyApp(ui, server)
# first.R
first_parse <- function(){  
  do.call(first_add, list())
}

first_add <- function(){  
  results <- eval(parse(text = "(function(){return(10)})()"))  
}
# second.R
second_summary <- function(){  
  print(results + 100)  
}

My expectations are that after pressing "First" and then "Second" buttons I should get 110, and these should be independent in every session. I've tried with <<- but when browser's two windows are opened with the app, results behaves like being shared.

I definitely think you are overthinking this. :slight_smile: Basically, you need to decide whether you want the value to be shared among sessions (i.e. live in the global environment) or if you want each session to have their own copy (where initial conditions are the same, perhaps?).

If you want the object to exist in the global environment and be shared, you define it outside of the ui / server functions (or in global.R). If you want the object to be instantiated per session, you define it in the server. A simple example based on what I understand you are expecting is below: derive initial conditions from a global object, but instantiate the object per session so they each have their own copy.

You might want to check out shiny.rstudio.com for more reading/articles on Shiny reactivity, and reactiveVal in particular.

library(shiny)

first <- 10
second <- 100

ui <- fluidPage(
  actionButton("first", label = "First"),  
  actionButton("second", label = "Second"),
  verbatimTextOutput("result")
)

server <- function(input, output, session) {
  results <- reactiveVal(0)
  
  observeEvent(input$first, {
    print(paste("Add",first))
    results(results() + first)
  })  
  
  observeEvent(input$second, {
    print(paste("Add",second))
    results(results() + second)
  })
  output$result <- renderText(paste0("Result: ",as.character(results())))
}

shinyApp(ui, server)
1 Like

First of all, thank for your answer!

Basing on your answer "object to be instantiated per session, you define it in the server" is something I've been looking for (this is the reason why I put results <- 0 in server part according to https://shiny.rstudio.com/articles/scoping.html ). The problem is how to access this variable from the functions that I used in the example. Unfortunately, usage of do.call and eval with parse are parts of the present application that I can't change.

Regarding application architecture - until now every "action" which does something and is trigerred by buttons worked separately using values stored in input. Now more complicated "action" was introduced (code with calculations taking some time), so this would be wise to store "results" of these in case of re-usage.

To show this as real example, let's say we have simple app with 3 numericInput-s and 4 actionButton-s (ids: a1, a2, a3, a4). Buttons "a1" and "a2" trigger simple calculations (using values from numericInput-s and executed via eval with parse) - so these can be rerun every time, because of short time of execution. Button "a3" triggers more complicated code - it takes for example 5 minutes (again using values from numericInput-s and executed via eval with parse). Code which is attached to "a4" in 90% is similar with the code of "a3" - just adds something at the end, thus when "a3" was triggered it would be wise to reuse results of "a3" in "a4" instead of calculating everything again. The question is how to store results from "a3", hence these can be accessed in "a4"?

To recap, my problem is how to access results from every parts of the app, taking into account that do.call and eval with parse are widely used in this app and can't be simplified and every "action" code is source-ed. In other words how to make my example work with all these do.call and eval with parse:)?

Thank you!

Edit:

I've just modified the code to reflect usage of reactiveVal() - I am not sure if implemented correctly.

# app.R
rm(list=ls())
library(shiny)

source("first.R")
source("second.R")

ui <- fluidPage(
  actionButton("first", label = "First"),  
  actionButton("second", label = "Second")  
)

server <- function(input, output, session) {
  results <- reactiveVal(0)
  observeEvent(input$first, {
    first_parse()
  })  
  observeEvent(input$second, {
    second_summary()
  })
}

shinyApp(ui, server)
# first.R
first_parse <- function(){  
  do.call(first_add, list())
}

first_add <- function(){  
  results(eval(parse(text = "(function(){return(10)})()")) )
}
# second.R
second_summary <- function(){  
  print(results() + 100)  
}

After pressing "first" button I get:

Warning: Error in results: could not find function "results"
Stack trace (innermost first):
    71: <Anonymous> [first.R#7]
    70: do.call
[...]

So quick question / aside before I have a chance to dig into this further. Do you have the ability to change the first and second functions? The functions are defined "badly" in that they take no parameters. There is a great read on functional programming (and lots of other R topics) here:

http://adv-r.had.co.nz/Functional-programming.html

The basic idea is that a function should be a self-contained unit. You pass in whatever parameters are necessary and then the function returns whatever values need to persist and be used elsewhere. These functions are trying to take variables from a different environment and alter them. It is much better (and WAY easier to manage) to have the functions perform a task with given inputs and then return without munging around in other environments.

I'm not sure if you have ever experienced SAS or SAS macro language, or maybe some other programming language. I know I struggled with a similar hurdle when first entering functional programming from languages that tended more towards a global environment, code templating and generation, etc.

EDIT: A few more thoughts... the "caching" question makes things more tricky. There are many ways you can do this. One is with the browser cache... this is least likely to help, but helps when sending information from server to client. The other is with the memoise package, which is more R-flavored and sounds like it may be a good fit for you. It presumes on functional programming, though, so it will be important to rearchitect your code into a more R-centric and functional programming approach. The last that comes to mind for me is handling the cache yourself with .rds files, a database, or something like that. This is the most cumbersome from a development perspective.

Ok. A version of your app that works...

# app.R
library(shiny)

#source("first.R")
# first.R
first_parse <- function(){  
  do.call(first_add, list())
}

first_add <- function(){  
  eval(parse(text = "(function(){return(10)})()"))
}

#source("second.R")
# second.R
second_summary <- function(input){  
  print(input + 100)  
  return(input+100)
}
ui <- fluidPage(
  actionButton("first", label = "First"),  
  actionButton("second", label = "Second"),
  verbatimTextOutput("vtext")
)

server <- function(input, output, session) {
  results <- reactiveVal(0)
  observeEvent(input$first, {
    # update value of results - replace with 10
    results(first_parse())
  })  
  observeEvent(input$second, {
    # update value of results - add 100
    results(second_summary(results()))
  })
  output$vtext <- renderText(paste("Results:",results()))
}

shinyApp(ui, server)

A few remaining thoughts:

  • Obviously your examples are contrived, but hopefully as you start reading / thinking about functional programming, you will see how liberating it is. I.e. your first function literally just returns the value 10. There may be cause for rearchitecting (read: removing needless complications) some of the code into separate functions (or an internal package) that perform individual tasks
  • For instance, rather than "code-generating" with eval and parse, it is more likely that functional programming will solve your issue of different parameters, etc.
  • You may profit from the discussion about the First line of every R script?
  • Notice what I did to the second function. This adheres to better "function" best-practices
  • The verbatimTextOutput is just there to show what is happening.
  • Rather than save the values in separate files, I thought it easier just to put the code where it would be sourced.
  • The results() function / reactiveVal only exists inside the server function, so it should not be directly referred to elsewhere.
4 Likes

Thank you very much for the comprehensive answer, working example and pointing out/reminding that breaking the functional programming paradigm is not the best way to go:). I have to introduce more changes instead of looking for the workaround. Thx for the reactiveVal() hint as "session - global" variable.

1 Like