Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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
like image 218
itpetersen Avatar asked Apr 13 '21 13:04

itpetersen


People also ask

What is the method in which modules interact with each other?

In software engineering, coupling is the degree of interdependence between software modules; a measure of how closely connected two routines or modules are; the strength of the relationships between modules.

What is Golem shiny?

A Golem is a package that includes a Shiny Application. Whether the interactive application is the core of the development or a tool for your package, {golem} will help you build a production-ready Shiny app.


2 Answers

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:

  • init https://github.com/ColinFay/golemexamples/blob/master/golemR6/R/app_server.R#L12
  • trigger https://github.com/ColinFay/golemexamples/blob/master/golemR6/R/mod_a.R#L43
  • watch https://github.com/ColinFay/golemexamples/blob/master/golemR6/R/mod_b.R#L22

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

like image 180
Colin FAY Avatar answered Oct 20 '22 03:10

Colin FAY


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
}
like image 31
Vincent Guyader Avatar answered Oct 20 '22 04:10

Vincent Guyader