app <- shinyApp(ui = movies_ui,
server = movies_server)
str(app)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.
RStudio and Positron display Run App and Run Shiny App buttons respectively in the Source pane. Clicking either button calls runApp() and launches the 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
--devmodeoption
runApp()is called with the full path to ourapp.Rfile and aportargument2
- 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.Rfiles” 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
Clicking Run Shiny App in Positron launches the application in devmode, which controls how Shiny behaves during development. See Positron’s runShinyApp.R for the devmode launcher implementation.
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):
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
-
appDiris 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():
app <- shinyApp(
ui = movies_ui,
server = movies_server
)
app$appOptions$appDir[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:4
runApp(appDir = "path/to/sap")Can also use a shiny.appobj directly:
app <- shinyApp(
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.
8.2.4 startApp()
Shiny recently introduced startApp(), which changes how we think about launching apps interactively. While runApp() blocks the R session (you cannot type in the console while the app is running), startApp() launches the app in the background and returns control to the console immediately.
I’ve found this matters most when you’re exploring your app interactively. Instead of restarting R each time you want to test a change, you can modify your code and re-run it while the app is still running in another process. The app stays live and you keep your REPL.
Below are some practical examples for using startApp():
Launch and stop a shiny app object:
app <- startApp(
appDir = shinyApp(
ui = movies_ui(),
server = movies_server),
port = 3838
)
app$stop()Launch with test mode enabled:
app <- startApp(
appDir = shinyApp(
ui = movies_ui(),
server = movies_server,
options = list(test.mode = TRUE)
),
port = 3838,
test.mode = TRUE
)Check app status and URL:
app <- startApp(
appDir = shinyApp(
ui = movies_ui(),
server = movies_server
),
port = 3838
)
app$status()
app$url()After the app launches, you keep your console and can continue working: run tests, modify code, re-source functions, or launch additional apps on different ports. Call handle$stop() to stop the background app when you’re done.
The key difference is simple: runApp() is blocking and single-process. startApp() is non-blocking and spawns a background process. When to use startApp() vs. runApp(): I recommend startApp() for interactive development (you need console access), and runApp() for traditional deployment (the blocking behavior is fine). In production or when you’re ready to fully focus on the running app, runApp() is still the right choice. But for rapid iteration in development, startApp() unlocks a faster workflow.
See the Shiny documentation for full details on startApp() arguments and capabilities.
Recap
shinyApp()is typically used to create apps within an interactive R sessionshinyAppDir()runs a Shiny app stored in a directory (containing anapp.Rfile (orui.Randserver.Rfiles)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.5startApp()launches an app in a background process and returns console control immediately, making it ideal for interactive development and rapid iteration.
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).
Launch app with the shinypak package:
launch('08_launch')8.3.1 App launching options
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.testmodeoption, orFALSEif the option is not set.’
The test.mode option 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 that defaults to an empty list() in launch_app():
launch_app <- function(options = list()) {
shinyApp(
ui = movies_ui(),
server = movies_server,
options = options
)
}- 1
-
Build the shiny app object with
movies_uiandmovies_server - 2
-
Include
optionslist
With the introduction of startApp(), we can add a mode argument to choose between blocking ("run") and non-blocking ("start") execution:
launch_app <- function(options = list(), mode = "run", run = "p") {
app <- shinyApp(
ui = movies_ui(),
server = movies_server,
options = options
)
if (mode == "start") {
shiny::startApp(appDir = app, port = 3838)
} else if (mode == "run") {
display_type(run = run)
shiny::runApp(appDir = app)
}
}- 1
- Create the shiny app object
- 2
- Non-blocking mode—launches in background process
- 3
- Blocking mode (default)—uses display_type() for window placement
Now you can call launch_app(mode = "start") for rapid iteration or launch_app(mode = "run") for traditional blocking execution.
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:6
#' 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
8.3.3 An updated launch_app()
The display_type(), options list, and mode argument have been added to launch_app():
launch_app <- function(options = list(), mode = "run", run = "p") {
app <- shinyApp(
ui = movies_ui(),
server = movies_server,
options = options
)
if (mode == "start") {
shiny::startApp(appDir = app, port = 3838)
} else if (mode == "run") {
display_type(run = run)
shiny::runApp(appDir = app)
}
}- 1
- Create the shiny app object
- 2
- Non-blocking mode—launches app in background, returns console control
- 3
- Blocking mode (default)—uses display_type() for window placement, blocks console
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
Ctrl/Cmd + Shift + D
Ctrl/Cmd + Shift + 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 Is this an interactive session?
The first condition we’ll 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 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()).7
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), mode = "run", 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)
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.8
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).
withr::with_options(new = list(shiny.autoload.r = FALSE), code = {
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), mode = "run", 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
Ctrl/Cmd + Shift + D
Ctrl/Cmd + Shift + B
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:
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(),runApp(), andstartApp().↩︎The
shiny.launch.browseroptions are covered in this blog post by Garrick Aden-Buie, and I’ve combined them into arunargument inlaunch_app()↩︎pkgloadis part of the conscious uncoupling of thedevtoolspackage we learned about back in Chapter 6.↩︎You can read more about the
shiny.autoload.roption andloadSupport()in this article↩︎















