shinyApp with ui and server in separate files? - r

Right now my shinyApp is running with four separate R files. app.R, server.R, ui.R, and global.R. This apparently is an old way of doing things but I like how it organizes my code.
I need to use the onStart parameter in the shinyApp() function. Because of the way I've separated my files, it looks like R knows to load the four files together when running the Run App button in R Studio. This means my app.R file only contains runApp().
I can't seem to use the onStart parameter with runApp(). And when I try to create a shinyApp(ui, server, onStart = test()) object and pass it through runApp() it can't find the test function.
### in global.R
test <- function(){
message('im working')
}
### in app.R
app <- shinyApp(ui, server, onStart = test())
runApp(app)
I found this in the R documentation. I'm not sure what they mean by using the global.R file for this?
https://shiny.rstudio.com/reference/shiny/latest/shinyApp.html
Thanks a ton, I hope this question makes sense.

From what I understand, the functionality you want can be achieved by both shinyAppDir and shinyApp. You just have to use them correctly.
If you have the 3 file structure namely, ui.R, server.R, and global.R. You should use shinyAppDir and not shinyApp. In global.R, you can define code you want to run globally, if it's in a function, you can define and then call that function inside the same file i.e. global.R. In order to run it using shinyAppDir, you need to give the directory where your application files are placed.
According to the same shinyApp reference you shared,
shinyAppDir(appDir, options = list())
If you want to use shinyApp instead, you need to have both ui and server inside the same file, and pass the object name to shinyApp function. Here, if you want to run some code globally, you need to first have that code defined inside a function in the same file, and then pass that function name as the onStart parameter. If your function name is test you need to pass it as shinyApp(ui, server, onStart = test) and not test(), but more importantly, you need to have all 3 (ui, server, and your global function i.e. test) inside the same file.
According to reference,
shinyApp(ui, server, onStart = NULL, options = list(), uiPattern = "/", enableBookmarking = NULL)

Related

List of file paths to iteratively pass through sourced python function in Shiny

I have come up with a python function that I have confirmed works just fine. I am trying to put this into a Shiny app using Shiny's reticulate. I am not super familiar with Shiny but need to use it anyhow.
To give a bit of background on what I am doing, I've written some python code that takes takes multiple files and matches strings based on one common list of strings. This code works fine when I run the python files on my machine.
I need to make this available to others using a shiny app, where they can upload their files, then have the app run the underlying python code.
So far, I have set up the shiny app so that it can take in multiple files. I am having a hard time thinking about how I can use reactive to make a list of the file path names to then send to my python code (which includes a step to open and read the file) so it can do its thing.
This is the code that I have for my app thus far:
library(shiny)
library(shinyFiles)
# define UI
ui <- fluidPage(
titlePanel('Counter of Gendered Language'),
fileInput("upload", "Choose a folder",
multiple = TRUE,
accept = c('text')),
tableOutput('text'),
downloadButton('output', 'Download Count File .csv'))
# define server behavior
server <- function(input, output){
# Setup
#* Load libraries
library(reticulate)
#* Use virtual environment for python dependencies
use_virtualenv('file/path/py_venv', required = TRUE)
#* Source code
source_python('code/counting_gendered_words.py')
#* Load list of words to match raw text against
dictionary <- read.csv('data/word_rating.csv')
text <- reactive(
list <- list.files(path = input$upload[['name']])
)
output$counted <- gendered_word_counter(dictionary, text())
output$downloadData <- downloadHandler(
filename = function(){
paste0(input$upload, ".csv")
},
content = function(file){
vroom::vroom_write(text$counted, file)
}
)
}
# Run the application
shinyApp(ui = ui, server = server)
What it tells me when I run this app is that:
Error : Operation not allowed without an active reactive context.
You tried to do something that can only be done from inside a reactive consumer.
So what I am wanting to do is basically just pass each file name that someone uploads to the app and pass that file's name into my gendered_word_counter() python function.
How would I go about this?
I'm super confident that I just am being a newbie and it is probably a super simple fix. Any help from those who are more comfortable with Shiny would be much appreciated!
Edit: I notice that my code is only calling the names of the files which is meaningless for me without the contents of the uploaded files! Would it be better if I read the files in the shiny app instead of in my .py file?
I can't reproduce the app without the python code, but i can see that this line:
output$counted <- gendered_word_counter(dictionary, text())
has a reactive object (text()) being called with no reactive context. It should be wrapped in observe or observeEvent.
observe({
output$counted <- gendered_word_counter(dictionary, text())
})
Also let's add the parenthesis here:
content = function(file){
vroom::vroom_write(text()$counted, file)
}

How to run Shiny app during RStudio project creation from template?

I am creating a custom RStudio Project Template as detailed here.
I have everything working to create a new project, and I also have the Shiny app working by itself.
However, I would now like to synchronously run the app in what amounts to my hello_world() function in the above webpage.
I can write wrapper functions around my Shiny app that work as desired outside of the context of making a new project from a template via the RStudio menus, but in the context of creating a new project, it is as if the line to run the app is not present as no app appears, and there are no messages, warnings, or errors issued.
# function works as expected outside context of creating a new project
run_app <- function() {
ui <- shiny::fluidPage(shiny::titlePanel("New Project"))
server <- function(input, output, session) {
session$onSessionEnded(function() {
shiny::stopApp()
})
}
shiny::shinyApp(ui = ui, server = server,
options = list(launch.browser = TRUE))
}
# but nothing Shiny related happens if called within the new project creation function
# the new project creation process continues as if the call to start the app is not present
hello_world <- function(path, ...) {
run_app()
}
Is it possible to run a Shiny app during project creation?
?shiny::shinyApp
Normally when this function is used at the R console, the Shiny app object is automatically passed to the print() function, which runs the app. If this is called in the middle of a function, the value will not be passed to print() and the app will not be run. To make the app run, pass the app object to print() or runApp().
The app runs correctly in its self contained function because the returned value is invisibly passed to print, but this does not happen when the function contains additional code.
Therefore, a solution is to wrap the function call in a print().
hello_world <- function(path, ...) {
print(run_app())
}

R Shiny Dashboard - Loading Scripts using source('file.R')

Introduction
I have created an R shiny dashboard app that is quickly getting quite complex. I have over 1300 lines of code all sitting in app.R and it works. I'm using RStudio.
My application has a sidebar and tabs and rather than using modules I dynamically grab the siderbar and tab IDs to generate a unique identifier when plotting graphs etc.
I'm trying to reorganise it to be more manageable and split it into tasks for other programmers but I'm running into errors.
Working Code
My original code has a number of library statements and sets the working directory to the code location.
rm(list = ls())
setwd(dirname(rstudioapi::getActiveDocumentContext()$path))
getwd()
I then have a range of functions that sit outside the ui/server functions so are only loaded once (not reactive). These are all called from within the server by setting the reactive values and calling the functions from within something like a renderPlot. Some of them are nested, so a function in server calls a function just in regular app.R which in turn calls another one. Eg.
# Start of month calculation
som <- function(x) {
toReturn <- as.Date(format(x, "%Y-%m-01"))
return(toReturn)
}
start_fc <- function(){
fc_start_date <- som(today())
return(fc_start_date)
}
then in server something like this (code incomplete)
server <- function(input, output, session) {
RV <- reactiveValues()
observe({
RV$selection <- input[[input$sidebar]]
# cat("Selected:",RV$selection,"\r")
})
.......
cat(paste0("modelType: ",input[[paste0(RV$selection,"-modeltype")]]," \n"))
vline1 <- decimal_date(start_pred(input[[paste0(RV$selection,"-modeltype")]],input[[paste0(RV$selection,"-modelrange")]][1]))
vline2 <- decimal_date(start_fc())
.......
Problem Code
So now when I take all my functions and put them into different .R files I get errors indicating the functions haven't been loaded. If I load the source files by highlighting them and Alt-Enter running them so they are loaded into memory then click on Run App the code works. But if I rely on Run App to load those source files, and the functions within them, the functions can't be found.
source('./functionsGeneral.R')
source('./functionsQuote.R')
source('./functionsNewBusiness.R')
source('./ui.R')
source('./server.R')
shinyApp(ui, server)
where ui.R is
source('./header.R')
source('./sidebar.R')
source('./body.R')
source('./functionsUI.R')
ui <- dashboardPage(
header,
sidebar,
body
)
Finally the questions
In what order does R Shiny Dashboard run the code. Why does it fail when I put the exact same inline code into another file and reference it with source('./functions.R')? Does it not load into memory during a shiny app session? What am I missing?
Any help on this would be greatly appreciated.
Thanks,
Travis
Ok I've discovered the easiest way is to create a subfolder called R and to place the preload code into that folder. From shiny version 1.5 all this code in the R folder is loaded first automatically.

Set www location in shiny::shinyApp

I am currently creating a shiny app that gets invoked with shiny::shinyApp via a wrapper function.
startApp <- function(param1, param2, ...){
# in fact, ui and server change based on the parameters
ui <- fluidPage()
server <- function(...){}
runApp(shinyApp(ui, server))
}
When I include resources (like images, videos etc.), I currently use the addResourcePath command and include the resources with a prefix. However, I would like to add a "default resource path" (appDir/www in usual apps). There seems to be no suitable parameter in shinyApp or runApp. Setting the working directory to the resource folder or one level above does not work either.
Here is a short MWE.
## ~/myApp/app.R
library(shiny)
shinyApp(
fluidPage(tags$img(src = "image.gif")),
server <- function(...){}
)
## ~/myApp/www/image.gif
# binary file
If I run the app via RunApp("~/myApp") everything works, but
setwd("~/myApp")
myApp <- shinyApp(source("app.R")$value)
runApp(myApp)
will fail to display the image. Any suggestions are appreciated.
Context
The reason I want to start the app based on an shiny.appobj (an object that represents the app) rather than a file path is, that the latter approach does not work well with passing parameters to an app. Here is a discussion about this topic.
The recommended way of passing parameters to an app that gets invoked by runApp("some/path") is as follows:
startApp <- function(param1, param2, ...) {
.GlobalEnv$.param1 <- param1
.GlobalEnv$.param2 <- param2
.GlobalEnv$.ellipsis <- as.list(...)
on.exit(rm(.param1, .param2, .ellipsis, envir = .GlobalEnv))
runApp("~/myApp")
}
This approach is just ugly IMO and I get warnings when I build the package that contains the app together with the startApp function. Those warnings occur because the package then breaks the recommended scoping model for package development.
In the help documentation in shiny::runApp, it says appDir could be either of the below:
A directory containing server.R, plus, either ui.R or a www directory
that contains the file index.html.
A directory containing app.R.
An .R file containing a Shiny application, ending with an expression
that produces a Shiny app object.
A list with ui and server components.
A Shiny app object created by shinyApp.
When you run via RunApp("~/myApp"), it is a directory containing app.R
If you want to run via a shiny app object created by shinyApp
you can try things like
myapp_obj <- shinyApp(
fluidPage(tags$img(src = "image.gif")),
server <- function(...){}
)
runApp(myapp_obj)
Update
create a script myapp_script.R with
shinyApp(
fluidPage(tags$img(src='image.gif')),
server <- function(...){}
)
and then call runApp("myapp_script.R")

Variables in Shiny to a sourced file via environment

Im trying to build a small shiny app that will call a sourced file once an actionButton is pressed. The actionButton observer will capture the input$topic and input$num from the ui.R and then call this source("downloadTweets.R") file that needs the topic and num variables defined in the environment to work properly.
# Entry shiny server function
shinyServer(function(input, output) {
observeEvent(input$searchButton, {
topic <- as.character(input$hashtagClass)
num <- as.numeric(input$numTweetsClass)
source("downloadTweets_Topic.R")
})
})
When I try to run it, there is an error message that outputs that topic value was not found once the source("downloadTweets_Topic.R") call is made. I'm fairly new to Shiny, I read the scope documentation and use the reactive() function, but I'm afraid that I don't really get how it works. Is there a way to do this or should I reimplement the .R file so I can pass these values to a function?
The reason I'm doing it like this is just merely code reusal from a different project in R Studio which is not a Shiny app.
Looks like the input$hashtagClass is missing. Throw a browser() line above that line but inside the observe expression. This'll drop you into a breakpoint when the app is run and this code is triggered. You can likely solve the issue with a req call. Look it up with ?req.
#pork chop's suggestion to add local=T to source is also important. This will put any assigned variables into the global env.

Resources