R R6Class global obj in shinyApp - r

In my shinyApp, i have one global var : an R6Class, when my app is finished i remove the var from the globalEnv:
#' #importFrom shiny reactive
#' #export
gui <- function (port = getOption("shiny.port"),
host = getOption("shiny.host", "127.0.0.1"),
working.directory = getwd()) {
appDir <- system.file("shiny", "gui", package = "lidRGUI")
if (appDir == "") {
stop("Could not find shiny directory. Try re-installing `lidRGUI`.", call. = FALSE)
}
catalogModele <<- CatalogModele$new()
csvPlotsModel <<- CSVPlotsModel$new()
on.exit({
rm(list = c("catalogModele", "csvPlotsModel"), pos = ".GlobalEnv")
gc()
})
shiny::runApp(appDir, display.mode = "normal")
}
#----------------------------------
# Catalog Modele Class
#----------------------------------
#' #importFrom R6 R6Class
#' #importFrom shiny reactiveValues
#' #export CatalogModele
CatalogModele <- R6Class(
public = list(
catalogs = reactiveValues(),
add_catalog = function(key,value) {
self$catalogs[[key]] <- value
},
get_catalog = function(key) {
return(self$catalogs[[key]])
},
finalize = function() {
print("Finalize has been called!")
}
)
)
The first time i launch gui() catalogModele$catalogs is empty but after the first launch catalogModele$catalogs countains all previous initilizations.

This behavior is documented in the R6 vignettes
If your R6 class contains any fields that also have reference semantics (e.g., other R6 objects, and environments), those fields should be populated in the initialize method. If the field set to the reference object directly in the class definition, that object will be shared across all instances of the R6 objects.
reactiveValues (as well as reactiveVal) have reference semantics as mentioned here. This is why the solution you posted gives the expected results while the code in your question leads to a "shared catalog" among all instances of the class.
Sorry I didn't think about this when I wrote the answer to this question of yours but I can correct it if you want.

I found a solution but i don't know why that's works. I replaced :
public = list(
catalogs = reactiveValues(),
by
public = list(
catalogs = NULL,
initialize = function() {
self$catalogs <- reactiveValues()
},

Related

Run `testServer()` w/ `golem::get_golem_options()`

I have set up a R Shiny app using the golem R-package. In the app_server() I use some globally set golem options as follows:
app_server <- function(input, output, session) {
myServer(
id = "test",
global_variable = golem::get_golem_options()$global_variable,
other_variable = other_variable
)
...
}
Now the default test testServer() in test-golem-recommended.R throws an error because within myServer() it can't find the golem options or the global_variable is NULL (which also kind of makes sense):
testServer(app_server, {
# Set and test an input
session$setInputs(x = 2)
expect_equal(input$x, 2)
})
However, is it possible to call app_server() so that the golem options are loaded first?
I encountered the same issue, and have an inelegant solution that involves setting the golem_options explicitly during the unit testing phase.
First, I defined a function to set the golem_options. This can be anywhere in your package, or defined in the testthat script:
#' Set golem options for unit testing purposes
#'
#' #param golem_opts list()
#'
#' #examples
#' \dontrun{
#' my_golem_options <-
#' list(
#' myconfig = "config01"
#' )
#' set_testing_golem_options(my_golem_options)
#' }
set_testing_golem_options <-
function (golem_opts)
{
current_golem_options = getShinyOption("golem_options")
for (n in names(golem_opts)) {
current_golem_options[[n]] <- golem_opts[[n]]
}
shinyOptions(golem_options = current_golem_options)
}
Then you want to do 3 things just before the testServer action in the test-golem-recommended.R:
Set your golem_options:
set_testing_golem_options(list(global_variable = 'somevalue'))
Alternatively, you could just make a list of options without the function listed above, but the function will respect any other shinyOptions that happen to be already set.
Explicitly define the MockShinySession that you're going to use for testing:
defined_mock_shiny_session <- MockShinySession$new()
Assign the options into the mock session:
defined_mock_shiny_session$options[['golem_options']] <-
getShinyOption("golem_options")
Now the trick is to call the testServer with the explicit MockShinySession that you've just created:
testServer(
app_server,
{
# Set and test an input
session$setInputs(x = 2)
expect_equal(input$x, 2)
},
session = defined_mock_shiny_session
)
That pattern works for me. I should probably explicitly unset any golem_options after the test is complete, or ensure that they revert back to their previous state in some way. But hopefully this will get you unstuck.
Here's everything all in one code block using the example you provided:
# Define the function to set golem_options
set_testing_golem_options <-
function (golem_opts)
{
current_golem_options = getShinyOption("golem_options")
for (n in names(golem_opts)) {
current_golem_options[[n]] <- golem_opts[[n]]
}
shinyOptions(golem_options = current_golem_options)
}
# Set the golem_options that you need
set_testing_golem_options(list(global_variable = 'somevalue'))
# Create the mock session
defined_mock_shiny_session <- MockShinySession$new()
# Assign the golem_options
defined_mock_shiny_session$options[['golem_options']] <-
getShinyOption("golem_options")
# Execute the test
testServer(
app_server,
{
# Set and test an input
session$setInputs(x = 2)
expect_equal(input$x, 2)
},
session = defined_mock_shiny_session
)

Function that imports data

Im trying to create a function that imports my data into R. Whenever I call this function though it runs through it but I don't see my data set anywhere
inputData <- function(){
teams <- read.csv('C:/Users/BT_1N3_24/Desktop/PremierLeaguePlayers.csv', header = TRUE)
currentTeam <- read.csv('C:/Users/BT_1N3_24/Desktop/TeamStats.csv', header = TRUE)
}
All functions are locally scoped in R. If you want to pass something out of the environment, it must be returned as an object. I would write your code like this:
inputData <- function(){
teams <- read.csv('C:/Users/BT_1N3_24/Desktop/PremierLeaguePlayers.csv', header = TRUE)
currentTeam <- read.csv('C:/Users/BT_1N3_24/Desktop/TeamStats.csv', header = TRUE)
list(teams, currentTeam)
}
## call the function
data.list <- inputData()

Pass objects to shiny app and launch with runApp

I am creating a package that contains a few interactive shiny apps. The purpose of these apps is to facilitate GUI exploration of in-memory objects. For example, I have an object consisting of discretized variables I would like to pass to the shiny app and then make adjustments via the GUI interface.
However, I am running into trouble when trying to access this in-memory object from the Shiny App.
Here is the relevant code:
First, I am wrapping the shinyServer function in another function. My thought here is to give the shiny server access to the passed object.
#' #export
appServer <- function(bins) {
su <- summary(bins)
shinyServer(function(input, output) {
## values that should trigger updates when changed
values <- reactiveValues(summary=su, i=1, bins=bins)
# excluded rest of body for brevity ...
}
In this function, I create a shinyApp object and pass in the ui (in another file) and the result of the appServer function defined above.
makeApp <- function(bins) {
shiny::shinyApp(
ui = appUI,
server = appServer(bins))
}
The preceding functions are called in this function that wraps the call to runApp and takes an argument from the user.
#' #export
adjust <- function(bins) {
## access data from the app?
app <- makeApp(bins)
shiny::runApp(app)
}
How can I pass an in-memory object to a shinyApp that is imported from another package?
When I execute the above code, I receive the following error:
ERROR: path[1]="C:\Users\myusername\AppData\Local\Temp\RtmpWMpvHT\widgetbinding23e8333e5298": The system cannot find the path specified
In the example below I demonstrate how you can pass an object x from the global environment or from any other environment to the shiny app and change its value. I'm not sure if this answers your question. It maybe prove useful anyway :)
library(shiny)
x <- 5
x
deparse(substitute(x)) # is going to do the trick
fun <- function(obj) {
# get the name of the passed object
object_to_change <- deparse(substitute(obj))
# get the object from a given environment
val <- get(object_to_change, envir = .GlobalEnv)
# ?environment
# Save the object as a reactive value
values <- reactiveValues(x = val)
# Now define the app that is going to change the value of x
ui <- shinyUI(fluidPage(
br(),
actionButton("quit", "Apply changes and quit"),
textInput("new", "", value = NULL, placeholder = "Set new value of x"),
textOutput("out")
))
server <- function(input, output) {
output$out <- renderPrint({
values$x
})
# change the value of x
observe({
req(input$new)
values$x <- as.numeric(input$new)
})
# Apply changes and quit
observe({
if (input$quit == 1) {
assign(x = object_to_change, value = values$x, envir = .GlobalEnv)
stopApp()
}
})
}
# Run the app
shiny::shinyApp(ui, server)
}
fun(x)
# Check the new value of x in the .GlobalEnv
x

Shiny - Why does Shiny not react to the result of an evaluated/parsed expression?

Please refer to this question for some of the functions and context.
The Code
Right now, I'm trying to "cure" this server code:
env = module_env$normalization
eval(envir= module_env$normalization, {
jjjj <- reactive({
eval(parse(text=paste(sep='', "input$", mod_id('var_echo'))))
})
####
#### vvvvvvvvvvvvvvvvvvvv
####
observeEvent(jjjj, {
# observeEvent(evalq(parse(text=paste(sep='', 'input$', mod_id('var_echo', env)))), {
print("HEY")
print(get('sum_exp', env))
})
####
#### ^^^^^^^^^^^^^^^^^^^^^^
####
observeEvent(input$print_mod_id, {
browser()
print(mod_id('var_echo', env))
print(paste("input$", mod_id('var_echo', env), sep=''))
print(parse(text=paste(sep='', "input$", mod_id('var_echo', env))))
print(eval(parse(text=paste(sep='', "input$", mod_id('var_echo', env)))))
})
})
which is meant to be paired with this UI code:
module_env$normalization <- new.env()
env = module_env$normalization
evalq(envir = env, {
#Create the UI here
ui_normalization <-
tabsetPanel(
tabPanel('Normalization (FPKM)',
p("normalization"),
actionButton(inputId = mod_id('var_echo', env), "Echo variables"),
actionButton("print_mod_id", "Print module UI")
)
)
print(mod_id('var_echo', env))
#Create your initial input/output variables here
sum_exp <- NULL
output_sum_exp <- NULL
#Always register last
register_module(
name="Normalization (FPKM)",
module=ui_normalization,
env=env,
inputs=c('sum_exp'),
outputs=c('output_sum_exp')
)
})
These two files will be called at separate times during the app, but ultimately, will be called using source("FILENAME.R", local=TRUE) to localize the environment of the source file to the global environment, which also holds the app.
The Problem
Most of this is for debugging, but the most important thing is that the expression jjjj is being read by Shiny (the app does run), but doesn't react to when the button is pressed.
mod_id() is a special function designed to react to the environment that it is called from to generate a special key based on a string and its calling environment:
###Sample results for the print_mod_id print statements
[1] "id0x000000001e9047f0__var_echo"
[1] "input$id0x000000001e9047f0__var_echo"
expression(input$id0x000000001e9047f0__var_echo)
[1] 2
attr(,"class")
[1] "integer" "shinyActionButtonValue"
Here's mod_id(string, environment), which returns a string:
mod_id <- function(name, envir = NULL) {
if(identical(envir, globalenv())) {
e = envir
} else if (is.null(envir)) {
e = parent.frame()
} else if (is.environment(envir)) {
e = envir
} else {
stop("given non-default argument \'envir\' is not an environment")
}
return(paste(sep = "",
"id",
sub('<environment: (.*)>', '\\1', capture.output(e)),
"__",
name
)
)
}
Testing Observations
Here's some of the tests I've done:
The unique IDs generated by mod_id() for the UI and server are indeed the same. I've already gone in and fixed any discrepancies, so they do match up.
The expression used to get the final input$INPUT_ID_FOR_SHINY name works in another case, except when the variable name is substituted with the function mod_id(). I'm not exactly sure why, but it might have to do with the evaluation order caused by the overarching eval statement.
The event triggered by jjjj is triggered once at the beginning of the app. I have no idea why this happens, but the fact is that it does.
Edit: Additional testing, now have the Shiny source code on my machine. The expression is now: evalq(as.name(paste(sep='', 'input$', mod_id('var_echo', env)))) but it still doesn't work.

tcltk R - how to access value returned by function

I just started with tcltk and R. And I am having troubles accessing a computed value by a function called myFun1 when calling a second function myFun2:
Here is a simplified version of my UI:
Simple tcltk interface
library(tcltk)
tt <- tktoplevel()
topMenu <- tkmenu(tt)
tkconfigure(tt, menu = topMenu)
fileMenu <- tkmenu(topMenu, tearoff = FALSE)
tkadd(fileMenu, "command", label = "Function1", command = myFun1)
tkadd(fileMenu, "command", label = "Function2", command = myFun2)
tkadd(topMenu, "cascade", label = "Tab", menu = fileMenu)
tkfocus(tt)
My functions
myFun1 <- function() {
compVal <- 2*3
compVal
}
myFun2 <- function() {
msg <- paste("The value is: \n", compVal )
mbval<- tkmessageBox(title="This is the title",
message=msg,type="yesno",icon="question")
}
Calling myFun1 works, but myFun2 returns
Error in paste("The value is: \n", compVal) :
Object 'compVal' not found
Also wrapping compVal into return(compVal) doesn`t work.
I was also thinking of doing:
res <- list(compVal=compVal)
but I am not able to access the created list with myFun2.
Any sugesstions on how to access the returned value form myFun1 inside myFun2?
I found a solution, at first I thought its not really a "clean" way of doing it, but even in the offical documentation it is done this way.
Simply create a global variable using <<- :
myFun1 <- function() {
compVal <<- 2*3
}

Resources