shinyApp(): can be bundled in a standalone app function to store an app-package
Also useful for quick prototyping or if an app is in a single .R script
Returns a shiny app object (helpful with testing)
runApp(): versatile function that can launch apps in multiple formats/locations
A directory with a server.R and ui.R, or a single app.R file`
An app object created by shinyApp()
shinyAppDir(): useful if you need launch a Shiny app programmatically from a directory.
This chapter covers the differences between shinyApp(), runApp(), and shinyAppDir(), what should go in an app.R file, and other options for launching an app (or apps) from within an app-package.
Positron Version
At the time of this writing, the 2024.09.0-1 pre-release of Positron was available for testing.
During development, we have a variety of options for launching applications. Both RStudio and Positron offer a single-click button to run our application the top of the Source pane/Editor. We can also launch the app by calling the standalone app function in the Console. Below we’ll cover what happens behind the scenes when we click on these icons.
Access the applications in this chapter
I’ve created the shinypak R package in an effort to make each section accessible and easy to follow. Install shinypak using pak (or remotes):
In the current branch of sap, the app.R file contains a call to launch_app(), which loads the package and passes the UI and server functions to shinyApp().
‘Launch’ vs. ‘Run’
I’ll be using the term ‘launch’ to differentiate successfully displaying the application locally (either in the IDE window, viewer pane, or browser) from clicking on the Run App/Run Shiny App button or calling the runApp() function.
As noted above, clicking Run App in RStudio calls runApp() and launches our application with a loadSupport() warning printed in the Console.
In Positron , clicking on the Run Shiny App button launches the application with a slightly different process than RStudio :
A new Shiny terminal process is started with the --devmode option
runApp() is called with the full path to our app.R file and a port argument2
The loadSupport() warning is displayed in the Terminal
It’s important to note that we also see the loadSupport() warning if we bypass the Run App/Run Shiny App buttons and call shiny::runApp() directly in the Console:
In the Shiny chapter we learned loadSupport() is called automatically when an application is run with runApp(). Specifically, loadSupport()
“loads any top-level supporting .R files in the R/ directory adjacent to the app.R/server.R/ui.R files” 3
Behind the scenes, runApp() is called by bothRun App/Run Shiny App buttons, which then automatically calls loadSupport(). This is why if we pass our standalone app function to the Console (which uses shinyApp(), not runApp()), the warning disappears:
The examples above demonstrate the differences between calling shinyApp() and runApp(), and the tension between calling a standalone app function from an R package vs. sourcing the app.R file in a Shiny project.
8.1.2--devmode
As we saw above, clicking Run Shiny App in Positron will launch the application in devmode,4 which is controlled by the script below:
commandArgs(trailingOnly = TRUE) retrieves the arguments passed to the script, excluding the default arguments used when R starts.
2
devmode <- "--devmode" %in% args checks if the --devmode flag is present among the arguments. If it is, devmode is set to TRUE
3
Lastly, the script runs the Shiny app using runApp() (with the specified path and port), and launch.browser = FALSE ensures the app is not automatically opened in a web browser.
Shiny devmode will display something like the following messages the first time your application is launched using the Run Shiny App button in Positron :
show/hide devmode options
shiny devmode - Using full shiny javascript file. To use the minified version, call `options(shiny.minified = TRUE)`This message is displayed once every 8 hours.shiny devmode - Turning off caching of Sass -> CSS compilation. To turn caching on, call `options(sass.cache = TRUE)`This message is displayed once every 8 hours.shiny devmode - Disabling the use of bslib precompiled themes. To be able to use precompiled themes, call `options(bslib.precompiled = TRUE)`This message is displayed once every 8 hours.shiny devmode - Enabling warnings about low color contrasts found inside `bslib::bs_theme()`. To suppress these warnings, set `options(bslib.color_contrast_warnings = FALSE)`This message is displayed once every 8 hours.
8.2 Shiny launch functions
In the following sections, we’ll compare shinyApp(), shinyAppDir(), and runApp() to determine which one to use in R/launch_app.R and the app.R file. As we’ve seen, the Run App/Run Shiny App buttons call runApp(), even when we have a call to shinyApp() in the app.R file. This might make you wonder,
“Why even include a call to shinyApp() if the app is being launched with runApp()?”
Let’s review what happens when we call shinyApp().
8.2.1shinyApp()
One of the key features of shinyApp() is the creation of the shiny.appobj (a shiny app object):
app <-shinyApp(ui = movies_ui, server = movies_server)str(app)
If we look at the structure of the returned object from shinyApp(), we see the shiny.appobj includes the appDir under appOptions:
List of 5$ httpHandler :function (req)$ serverFuncSource:function ()$ onStart : NULL$ options : list()$ appOptions :List of 2..$ appDir : chr "/path/to/sap"..$ bookmarkStore: NULL- attr(*,"class")= chr "shiny.appobj"
1
appDir is the first argument of runApp()
2
A shiny app object
This is why runApp() works with any .R file creating a shiny app object.
shinyApp()
shinyApp(): Creates and launches an app defined inline within the call itself (or with UI and server functions passed as arguments).
shinyApp(ui =fluidPage(# UI elements ),server =function(input, output) {# Server logic })
8.2.2shinyAppDir()
shinyAppDir() is similar to shinyApp(), but is designed to use a “path to directory that contains a Shiny app.”
In practice, we can use shinyAppDir() with a returned object from shinyApp():
This final method does not produce the loadSupport() warning because a Shiny object (shiny.appobj) has already been created, and runApp() is essentially calling print(app) in the Console.
runApp()
runApp(): A generalized way to launch your app–it can run apps defined inline, or from a directory.
shinyApp() is typically used to create apps within an interactive R session
shinyAppDir() runs a Shiny app stored in a directory (containing an app.R file (or ui.R and server.R files)
runApp() also launches apps in a directory, but it’s versatility makes it suitable for running apps in various formats, either defined inline or in separate directories.6
8.3 Standalone function
I tend to name the standalone app function launch_app() and include the options covered below (none of these are required, though).
shinyApp() or shinyAppDir() both have an options argument, so we should make this available in launch_app(). For example, one of the Shiny options we could include in our standalone app function is test.mode:
‘Should the application be launched in test mode? This is only used for recording or running automated tests. Defaults to the shiny.testmode option, or FALSE if the option is not set.’
Build the shiny app object with movies_ui and movies_server
2
Include options list
8.3.2 Where to launch?
I’ve written a display_type() helper function to 1) check if the application is being run in RStudio and, if so, 2) control where the Shiny app is launched. The run argument in display_type() takes the following options:
"p" = Viewer Pane
"w" = IDE Window
"b" = External browser
The option to access configuration is shiny.launch.browser:7
#' Shiny app display mode helper#'#' @param run where to launch app: #' * `"p"` = launch in viewer pane #' * `"b"` = launch in external browser #' * `"w"` = launch in window (default)#'#' @return notification of `shinyViewerType` option#' #' @export#'display_type <-function(run ="w") {if (interactive()) {if (Sys.getenv("RSTUDIO") =="1") {switch( run,p =options(shiny.launch.browser = .rs.invokeShinyPaneViewer),b =options(shiny.launch.browser = .rs.invokeShinyWindowExternal),w =options(shiny.launch.browser = .rs.invokeShinyWindowViewer),NULL =options(shiny.launch.browser =NULL) ) environment <-"RStudio" shinyViewerType <-getOption('shiny.launch.browser') |>attributes() |>unlist() |>unname() cli::cli_alert_info("App running in {environment}") cli::cli_alert_info("shinyViewerType set to {shinyViewerType}") } else { environment <-"RStudio" cli::cli_alert_info("App not running in {environment}") } } else { cli::cli_alert_info("App not running in interactive session") }}
1
Detect IDE
2
Set option
3
Print IDE
4
Print option
New dependency!
The cli package is imported as part the devtools/usethis workflow but we want to add it the Imports field of the DESCRIPTION with usethis::use_package('cli').
8.3.3 An updated launch_app()
The display_type() and options list have been added to launch_app():
After loading, documenting, and installing our package, we can see the documentation for our updated launch_app() function in RStudio and Positron :
Ctrl/Cmd + Shift + L / D / B
Standalone app function in RStudio
Standalone app function in Positron
In RStudio , we will confirm the app is launching without the loadSupport() warning, the message is printing to the Console from the run argument, and test.mode are implemented correctly.
We also want to confirm launch_app() works in Positron :
The updated launch_app() function launches the application in the Viewer
The updated launch_app() function in Positron
When we’re confident our standalone app function is working, we’ll write the app.R file.
8.4 The app.R file
The app.R file should contain any options or settings that we would use during development. There are multiple ways to launch an application from app.R, so I encourage you to explore the options below to find a method that works for your workflow/environment.
You have probably seen control flow like this in shiny help file examples (like flowLayout() below):
(a) if (interactive()) in flowLayout() example
Figure 8.1: Running examples ‘interactively’ let’s us see the app demo beneath the code
Adding if and interactive() will allow our app.R file distinguish between 1) launching apps from a RStudio/Positron session (i.e., during development) and 2) deploying apps (i.e., like publishing the application on Posit Connect).
8.4.1.1 Non-interactive sessions
If the session is non-interactive, we’ll divert all regular output to the standard error stream. The sink() function “diverts R output to a connection”, so the code below sends the output that would normally print to the console to the where error messages are written.
if (!interactive()) {sink(stderr(), type ="output")} else {}
This is useful in a non-interactive settings if we want to re-direct the error output for the console text-mode connection.
tryCatch() is used for ‘catching conditions’ during the execution of an expression (expr =):
if (!interactive()) {sink(stderr(), type ="output")tryCatch(expr = {library(sap) }, error =function(e) { pkgload::load_all() })} else {}
In this case, if library(sap) throws an error, the function specified after error = is executed (i.e., pkgload::load_all()).8
Written this way, in a non-interactive R session, app.R will re-direct the error output and attempt to load and attach sap, and if this fails, app.R will attempt to load all the files in the R/ folder.
8.4.1.2 Interactive sessions
If the session is interactive, we want app.R to load all the code in the R/ folder with pkgload::load_all() before launching our app with launch_app() (and a few optional arguments).
if (!interactive()) {sink(stderr(), type ="output")tryCatch(expr = {library(sap) },error =function(e) { pkgload::load_all() } )} else { pkgload::load_all()} sap::launch_app(options =list(test.mode =TRUE), run ='p')
pkgload::load_all() is the function that’s called when we run devtools::load_all() (or use Ctrl/Cmd + Shift + L), and this is somewhat analogous to running library(sap)
New dependency!
The pkgload package is imported as part the devtools/usethis workflow, otherwise we’d want to add it the Imports field of the DESCRIPTION with usethis::use_package('pkgload').
8.4.1.3 Dealing with loadSupport()
The withr package is designed to ‘run code with safely and temporarily modified global state’, and it comes in handy when launching shiny apps. For example, I want to turn off the loadSupport() behavior when launching the app from app.R.9
We can use withr::with_options() to accomplish this using the following new and code arguments
new: a named list of the new options and their values
code: the ’Code to execute in the temporary environment
We’ll place the withr::with_options() at the top of app.R and pass shiny.autoload.r = FALSE to the new argument (the contents of our app.R are passed into the code argument).
Now that we’ve updated the app.R to account for the app-package structure, we’ll load, document, and install sap and send it’s contents to the Console:
Ctrl/Cmd + Shift + L / D / B
New dependency!
The withr package is imported as part the devtools/usethis workflow, otherwise we’d want to add it the Imports field of the DESCRIPTION with usethis::use_package('withr').
8.4.2 Updated app.R
When we send the contents of app.R to the Console, the loadSupport() options are applied before running the app:
In the documentation for loadSupport(), you’ll find a second option for removing the R/ directory sourcing behavior: placing a _disable_autoload.R file in the R/ directory (this is also one of the behaviors of the golem framework, which we will cover in the following chapters).
Both methods work–this chapter demonstrates a way to remove the loadSupport() warning without having to add this file.
Recap
This chapter has covered some options for launching your app within your app-package. We went over what to include in the standalone app function and the app.R file, the differences between shinyApp(), shinyAppDir() and runApp(), but it’s worth exploring these topics further (especially if you plan on having more than one apps in your package).
Recap: launching your app
shinyApp():
shinyApp() doesn’t care about file structure, so it’s useful for quick prototyping or if the app is in a single .R script. It’s also more portable because you can share your app with a single .R script.
It’s possible to bundle shinyApp() in a wrapper function to integrate within an R package (like we’ve done with launch_app()).
shinyApp() returns a shiny app object, which can be useful if you want to explore the app structure programmatically (i.e., testing).
shinyAppDir():
shinyAppDir() launches an app from a directory, and is useful if an app is spread across multiple files and folders.
runApp()
runApp() a more generalized way to launch an app. It works with:
apps contained in a directory (i.e., runApp('path/to/app')
apps in separate ui.R and server.R files (or a single app.R file)
a shiny app object (passed to the appDir argument).
Clicking on Run App will run the application with runApp() if is detects a file (or files) that creates a shiny object.
In the following chapter, we’re going to cover where to put non-standard R package files that are common to Shiny apps (images, .css/.scss file, etc.).