# install.packages('pak')
::pak('mjfrigaard/shinypak') pak
8 Launch
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.
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.
8.1 ‘Running’ apps
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()
.
See the 06.2_pkg-imports
branch of sap
.
As we can see in the image below, this places the Run App icon in RStudio’s Source pane. In Positron , the app.R
file displays the Run Shiny App.
Clicking on either Run App/Run Shiny App buttons calls runApp()
and launches our application, but with a loadSupport()
warning.
The section below will cover the loadSupport()
warning and why it appears when using the Run App/Run Shiny App buttons in RStudio and Positron .
8.1.1 loadSupport()
warnings
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 ourapp.R
file and aport
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:
What’s with the warning?
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 theapp.R
/server.R
/ui.R
files” 3
Behind the scenes, runApp()
is called by both Run 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:
show/hide Positron’s runShinyApp.R script
<- "Usage: Rscript runShinyApp.R <path> <port> [--devmode]"
usage
<- commandArgs(trailingOnly = TRUE)
args
if (length(args) < 2) {
stop(usage)
}
<- args[1]
path <- as.integer(args[2])
port stopifnot(is.integer(port))
<- "--devmode" %in% args
devmode
if (devmode) {
::devmode()
shinyelse {
} options(shiny.autoreload = TRUE)
}
message("Running Shiny app")
message("-----------------")
message(sprintf('shiny::runApp(%s, port = %d)\n', deparse(path), port))
::runApp(path, port = port, launch.browser = FALSE) shiny
- 1
-
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 toTRUE
- 3
-
Lastly, the script runs the Shiny app using
runApp()
(with the specifiedpath
andport
), andlaunch.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.1 shinyApp()
One of the key features of shinyApp()
is the creation of the shiny.appobj
(a shiny
app object):
<- shinyApp(ui = movies_ui,
app 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 ofrunApp()
- 2
- A shiny app object
This is why runApp()
works with any .R
file creating a shiny app object.
8.2.2 shinyAppDir()
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()
:
<- shinyApp(
app ui = movies_ui,
server = movies_server
)$appOptions$appDir app
[1] "path/to/sap"
This path can be passed to the appDir
argument (along with any Shiny options
).
shinyAppDir(
appDir = app$appOptions$appDir,
options(test.mode = TRUE)
)
8.2.3 runApp()
The reason we’re able to call runApp()
in the Console to launch both applications above is because it’s very versatile:
runApp()
Can launch apps from ui.R
/server.R
, app.R
or directory:
# In console
runApp()
Works with a path to an .R
file that creates a shiny.appobj
:5
runApp(appDir = "path/to/sap")
Can also use a shiny.appobj
directly:
<- shinyApp(
app ui = movies_ui,
server = movies_server)
runApp(appDir = app)
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.
Recap
shinyApp()
is typically used to create apps within an interactive R sessionshinyAppDir()
runs a Shiny app stored in a directory (containing anapp.R
file (orui.R
andserver.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 app function
We’ll name our standalone app function launch_app()
and include the options covered below:
Launch app with the shinypak
package:
launch('08_launch-app')
8.3.1 interactive()
The first function option we’ll include is a check to see if there is a “human operator to interact with” with base::interactive()
:
if (interactive()) {
else {
}
}
You have probably seen control flow like this in shiny
help file examples (like flowLayout()
below):
Adding if
and interactive()
will allow our standalone app function to distinguish between 1) launching the app from a RStudio/Positron session (i.e., during development) and 2) deploying the application (i.e., like publishing the application on Posit Connect).
8.3.2 options
An options
argument can be passed to shinyApp()
or shinyAppDir()
. 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, orFALSE
if the option is not set.’
test.mode
lets us export values from our application when we’re running tests (which we’ll cover in-depth in the Tests section).
We’ll make options
an argument (defaulting to an empty list()
) in our updated launch_app()
function.
<- function(options = list()) {
launch_app shinyApp(
ui = movies_ui(),
server = movies_server,
options = options
) }
- 1
-
Build the shiny app object with
movies_ui
andmovies_server
- 2
-
Include
options
list
8.3.3 shiny.launch.browser
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
#'
<- function(run = "w") {
display_type 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))
<- "RStudio"
environment <- getOption('shiny.launch.browser') |>
shinyViewerType attributes() |>
unlist() |>
unname()
::cli_alert_info("App running in {environment}")
cli::cli_alert_info("shinyViewerType set to {shinyViewerType}")
clielse {
} <- "RStudio"
environment ::cli_alert_info("App not running in {environment}")
cli
} }
- 1
- Detect IDE
- 2
-
Set option
- 3
-
Print IDE
- 4
- Print option
8.3.4 Updates to launch_app()
The interactive()
check, display_type()
, and test.mode
options have been added to launch_app()
:
<- function(options = list(), run = "w") {
launch_app if (interactive()) {
display_type(run = run)
}shinyApp(
ui = movies_ui(),
server = movies_server,
options = options
) }
- 1
-
Check if interactive (Workbench) launch
- 2
-
Set
shinyViewerType
option withdisplay_type()
- 3
- 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
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 :
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.
8.4.1 Non-interactive sessions
What if the session isn’t interactive?
In this case, we’ll divert all regular output to the standard error stream.
8.4.1.1 stderr()
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.
8.4.1.2 tryCatch()
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) {
}, ::load_all()
pkgload
})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.2 Interactive sessions
If the session is interactive (i.e., !interactive() == FALSE
), we want app.R
to load all the code in the R/
folder with pkgload::load_all()
.
8.4.2.1 pkgload
pkgload::load_all()
is the function that’s actually called when we run devtools::load_all()
(or use Ctrl/Cmd + Shift + L), and this is somewhat analogous to running library(sap)
if (!interactive()) {
sink(stderr(), type = "output")
tryCatch(expr = {
library(sap)
error = function(e) {
}, ::load_all()
pkgload
})else {
} ::load_all()
pkgload }
8.4.2.2 withr
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 contents of app.R
into the code
argument. Just to be sure no options for shiny.autoload.r
previously exist, we’ll also set this option to NULL
before executing the rest of the code.
::with_options(new = list(shiny.autoload.r = FALSE), code = {
withrif (!interactive()) {
sink(stderr(), type = "output")
tryCatch(
expr = {
library(sap)
},error = function(e) {
::load_all()
pkgload
}
)else {
} ::load_all()
pkgload
}::launch_app(
sapoptions = list(test.mode = TRUE), run = 'p')
})
- 1
-
Turn off
loadSupport()
- 2
-
Define non-interactive behaviors
- 3
-
Define interactive behaviors
- 4
- Launch app (with options)
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
8.4.3 Updated app.R
When we send the contents of app.R
to the Console, the loadSupport()
options are applied before running the app:
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).
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.).
If you recall, we covered the
loadSupport()
function in Chapter 2.↩︎I’ve displayed the application in the browser clicking on the ‘Open the current URL in the default browser’ icon.↩︎
loadSupport()
does this by sourcing the files in alphabetical order.↩︎Shiny’s examples are run using
runApp()
↩︎It’s worthwhile to read the documentation on
shinyApp()
,shinyAppDir()
, andrunApp()
.↩︎The
shiny.launch.browser
options are covered in this blog post by Garrick Aden-Buie, and I’ve combined them into arun
argument inlaunch_app()
↩︎pkgload
is part of the conscious uncoupling of thedevtools
package we learned about back in Chapter 6.↩︎You can read more about the
shiny.autoload.r
option andloadSupport()
in this article↩︎