How to include R6 objects to share data across modules in golem Shiny app
Asked Answered
B

2

7

I’m trying to create a Shiny app using golem for the first time. golem structures Shiny apps with modules to help keep large Shiny apps modularized. However, modules don’t communicate with each other by default. I’d like to share data across modules. According to the golem documentation, R6 objects are a useful way to share data across modules.

However, in the example provided in the golem documentation, it is unclear where to put the R6 generator. According to Appsilon, the R6 generator goes in a separate .R file (e.g., logger_manager.R), and places a call in global.R to construct a new object from the class: logger_manager = LoggerManager$new(). However, there is no global.R file in a golem-based Shiny app.

Below is a minimal example of my golem-based Shiny app. I tried to follow the structure in the example provided in the golem documentation, but it does not seem to be sharing data across modules:

app_ui.R:

app_ui <- function(request) {
  tagList(
    # Leave this function for adding external resources
    golem_add_external_resources(),
    
    # List the first level UI elements here 
    fluidPage(
      mod_a_ui("a_ui_1"),
      mod_b_ui("b_ui_1")
    )
  )
}

golem_add_external_resources <- function(){
  
  add_resource_path(
    'www', app_sys('app/www')
  )
 
  tags$head(
    favicon(),
    bundle_resources(
      path = app_sys('app/www'),
      app_title = 'Test'
    ),
    # Add here other external resources
    # for example, you can add shinyalert::useShinyalert()
    shinyjs::useShinyjs()
  )
}

app_server.R:

app_server <- function( input, output, session ) {
  
  # Generate R6 Class
  QuestionnaireResponses <- R6Class(
    classname = "QuestionnaireResponses",
    public = list(
      resp_id = NULL,
      timezone = NULL,
      timestamp = NULL,
      gender = NULL,
    )
  )
  
  # Create new object to share data across modules using the R6 Class
  questionnaire_responses <- QuestionnaireResponses$new()
  
  # List the first level callModules here
  callModule(mod_a_server, "a_ui_1", questionnaire_responses)
  callModule(mod_b_server, "b_ui_1", questionnaire_responses)
}

mod_a.R:

mod_a_ui <- function(id){
  ns <- NS(id)
  
  tagList(
    radioButtons(inputId = "gender",
                 label = "What is your sex?",
                 choices = c("Male" = 1,
                             "Female" = 2),
                 selected = character(0))
)
}

mod_a_server <- function(input, output, session, questionnaire_responses){
  ns <- session$ns
  
  # Add time start to the output vector
  timestamp <- format(Sys.time(), "%Y-%m-%d %H:%M:%OS6")
  timezone <- Sys.timezone()
  
  # Generate a survey-specific ID number
  resp_id <- paste0(sample(c(letters, LETTERS, 0:9), 10), collapse = "")
  
  # Assign values to R6 object
  questionnaire_responses$resp_id <- resp_id
  questionnaire_responses$timezone <- timezone
  questionnaire_responses$timestamp <- timestamp
  questionnaire_responses$gender <- input.gender
}

mod_b.R:

mod_b_ui <- function(id){
  ns <- NS(id)
  tagList(
    print("questionnaire_responses$resp_id")
  )
}

mod_b_server <- function(input, output, session, questionnaire_responses){
  ns <- session$ns
  }

However, the data must not be being shared across modules because when I try to print resp_id in module B (which was generated in module A), I receive the following error:

An error has occurred!
object 'questionnaire_responses' not found
Blackwood answered 13/4, 2021 at 13:32 Comment(0)
M
5

So the issue here is that you are trying to print the object from the UI function when your R6 object is passed to the server function.

In other words, you're trying to do a print in the mod_b_ui function, when the R6 object is passed to the mod_b_server function.

For your original question, you can create the R6 class inside a standard file, as in https://github.com/ColinFay/golemexamples/blob/master/golemR6/R/R6.R

Note though that your data object is not reactive and will not reprint when you change in module A — this is where you can use {gargoyle} for example:

Please see https://github.com/ColinFay/golemexamples/tree/master/golemR6 for a full working example.

PS : there is a missing ns() in your module :)

Colin

Madox answered 13/4, 2021 at 19:30 Comment(2)
Thanks for all of these suggestions and the code example--that's very helpful. Is it possible to share the R6 object with the UI of module B so that I can print it to the user?Blackwood
@Blackwood Sure, but the issue is that you have to update it from the server side — the UI is generated once at application launch, so if you want to update it you need to do something like renderUI() or insertUI() or textOutput() as in github.com/ColinFay/golemexamples/blob/master/golemR6/R/…Madox
Q
3

I made some correction to you code (some typo)

tl;dr : you cant acces questionnaire_responses in the UI.

app_ui.R:

same as you

app_server.R:

#' @import shiny
#' @import R6
#' @noRd
app_server <- function( input, output, session ) {
  
  # Generate R6 Class
  QuestionnaireResponses <- R6Class(
    classname = "QuestionnaireResponses",
    public = list(
      resp_id = NULL,
      timezone = NULL,
      timestamp = NULL,
      gender = NULL
    )
  )
  
  # Create new object to share data across modules using the R6 Class
  questionnaire_responses <- QuestionnaireResponses$new()
  
  # List the first level callModules here
  callModule(mod_a_server, "a_ui_1", questionnaire_responses)
  callModule(mod_b_server, "b_ui_1", questionnaire_responses)
}

mod.R:

mod_a_ui <- function(id){
  ns <- NS(id)
  
  tagList(
    radioButtons(inputId = ns("gender"),
                 label = "What is your sex?",
                 choices = c("Male" = 1,
                             "Female" = 2),
                 selected = character(0))
  )
}

mod_a_server <- function(input, output, session, questionnaire_responses){
  ns <- session$ns
  
  # Add time start to the output vector
  timestamp <- format(Sys.time(), "%Y-%m-%d %H:%M:%OS6")
  timezone <- Sys.timezone()
  
  # Generate a survey-specific ID number
  resp_id <- paste0(sample(c(letters, LETTERS, 0:9), 10), collapse = "")
  
  # Assign values to R6 object
  questionnaire_responses$resp_id <- resp_id
  questionnaire_responses$timezone <- timezone
  questionnaire_responses$timestamp <- timestamp
  questionnaire_responses$gender <- "input$gender"
}


mod_b_ui <- function(id){
  ns <- NS(id)
  tagList(
    print("questionnaire_responses$resp_id") # you can't access questionnaire_responses in the UI
  )
}

mod_b_server <- function(input, output, session, questionnaire_responses){
  ns <- session$ns
  print(questionnaire_responses$resp_id) # but here you can
}
Quagmire answered 13/4, 2021 at 19:22 Comment(2)
and you can put your QuestionnaireResponses <- R6Class(... inside an R script in the R folder.Quagmire
That makes sense--thanks very much for the help. Is it possible to share the R6 object with the UI of module B so that I can print it to the user?Blackwood

© 2022 - 2024 — McMap. All rights reserved.