ETC5523: Communicating with Data

Tutorial 11: Package and audit a Shiny app

Author

Michael Lydeamore

Published

1 November 2001

🎯 Objectives

By the end of this tutorial, you will be able to:

  • add a package function that launches a Shiny app; and
  • audit an app before web deployment.

Install the packages used in this tutorial if you do not already have them:

install.packages(c("devtools", "rsconnect", "usethis"))

Bring the etc5523-wpl-share project you used in the workshop. If you missed the workshop, or would like a clean copy containing everything completed in class, clone the workshop-progress branch:

#| eval: false
#| echo: true
git clone --branch workshop-progress --single-branch \
  https://github.com/numbats/etc5523-wpl-share.git

Open wplshare.Rproj, restart R, then install any dependencies declared by the package:

devtools::install_deps(dependencies = TRUE)

Run the app and confirm that the number, plot and table agree when Minimum runs is exactly 150 in season 2024/25:

shiny::runApp("inst/app")
Important

In the workshop, we completed Steps 1–3: running the app, inspecting its reactive graph and replacing the repeated filters with selected_batters(). This tutorial continues from that point with Steps 4 and 5.

4. Make the app launchable from the package

The repository already has the structure of an R package. Files under inst/app will be installed with it.

Create R/run_app.R, then add a documented, exported run_app() function:

usethis::use_r("run_app")

The function should:

  1. locate the installed app directory with system.file(); and
  2. pass that directory to shiny::runApp().

Generate the documentation, install the package and test the launcher:

devtools::document()
devtools::install()
wplshare::run_app()

Why would a path such as /Users/name/wpl-share/inst/app fail for another package user?

Package launcher

#' Run the WPL batting explorer
#'
#' @return No return value; called for its side effect of launching the app.
#' @export
run_app <- function() {
  app_dir <- system.file("app", package = "wplshare")
  shiny::runApp(app_dir)
}

inst/app becomes app at the top level of the installed package. system.file() finds that location in the active R library rather than assuming where the source repository exists on a particular computer.

5. Audit the web deployment

Treat the hosting service as a clean Linux computer that receives only the app bundle. Mark each item ready, not ready, or not applicable:

  • all required files are inside inst/app;
  • file paths are relative and match the exact case of the file names;
  • package dependencies can be detected from the app code;
  • there is no install.packages() call in the app;
  • no password, token or secret is stored in the repository;
  • every possible input state produces a useful response; and
  • any durable writes use storage outside the app’s local filesystem.

Ask rsconnect what it detects:

rsconnect::appDependencies("inst/app")

Which audit item does the current app handle least well? Make one improvement and rerun the app.

Audit findings

The data are bundled under inst/app/data, paths are relative, packages are loaded explicitly, and the source contains no installation calls or secrets.

The empty selection is poorly communicated. req() prevents an error in the leader box but leaves it blank. One possible replacement is:

output$leading_batter <- renderText({
  selected <- selected_batters()

  if (nrow(selected) == 0) {
    return("No batter meets this threshold")
  }

  selected$striker[which.max(selected$runs)]
})

A deployed app should use a database, object store or other persistent service for durable writes because hosted instances can restart or be replaced.

Extra practice: a clean-room handover test

Exchange repositories with a partner. Open your partner’s project in a fresh R session and test whether the package can be installed and run without relying on their source directory or your installed copy of wplshare.

Create a temporary package library, install the package into it, then launch the app from that installation:

test_library <- file.path(tempdir(), "wplshare-library")
dir.create(test_library, showWarnings = FALSE)

devtools::install(lib = test_library)

library(wplshare, lib.loc = test_library)
wplshare::run_app()

Record any missing dependency, path assumption or unclear setup instruction. Fix one issue, restart R and repeat the handover test.

Note

Installing into test_library prevents your partner’s package from replacing the usual installed copy of wplshare. The temporary installation is removed when R clears its temporary directory.

Expected result and troubleshooting

A successful handover has three observable results:

  1. devtools::install() completes without an error;
  2. system.file("app", package = "wplshare") returns a path inside the temporary library rather than the source project; and
  3. wplshare::run_app() launches an app whose inputs and four outputs work.

Useful diagnoses include:

  • an empty result from system.file() means the installed package does not contain the expected app directory, or the package name is incorrect;
  • run_app() not being exported usually means @export is missing or devtools::document() has not regenerated NAMESPACE;
  • an error about a missing package means that dependency needs to be declared in DESCRIPTION and installed; and
  • a file-not-found error usually reveals an absolute path, incorrect relative path or mismatch in filename case.

The source repositories do not need to be identical. The test passes when a partner can install the package and launch the app without editing paths or moving files.