17 Shiny apps

Shiny apps can be submitted to Bioconductor as software packages or as documented and tested functions within packages.

17.1 Code organisation

Shiny apps are divided into two parts:

  • User interface (UI)
  • Server

Shiny apps submitted to Bioconductor must store the code for both the UI and server code under the R/ directory of the package.

The bulk of the package code should not be implemented directly within the shinyApp() function call. Instead, internal package functions should be developed to produce and return individual components of the UI and server sides of the Shiny app.

Shiny apps can also be written using shiny modules to allow for better code organisation and re-usability.

We recommend the following file naming scheme for source files:

  • Place internal functions that create observers – e.g., shiny::observeEvent() – in files named observers_*.R. Do this both for observers reacting to the Shiny server input object, and reactive values stored in lists of reactive values (e.g., shiny::reactiveValues()).
  • Place internal functions that create UI elements – e.g., shiny::checkboxInput() – in files named interface_*.R.
  • Place internal functions that update the Shiny server output object in files named outputs_*.R.
  • Place internal functions that perform miscellaneous processing steps in files named utils_*.R.

17.2 Running app

Functions in the package should return Shiny apps, but not launch them.

In other words, the function shiny::runApp() should not be found anywhere in the package source code. Instead, users should be left to call that function, using Shiny app objects returned by the package functions and options that control how the app is run (e.g., launch.browser = TRUE).

For instance, the package source code should look as follows:

build_app <- function(...) {
  ui <- .build_ui(...)
  server <- .build_server(...)
  app <- shinyApp(ui = ui, server = server)
}

While the user’s code should look as follows:

app <- build_app(...)
shiny::runApp(app, ...)

17.3 Building the package

Packages with shiny apps should be built using the standard R CMD build command. The package structure should follow a standard R package structure, with the Shiny app code stored under the R/ directory.:

MyShinyPackage/
|-- app.R
|-- DESCRIPTION
|-- NAMESPACE
|-- R/
|   |-- interface_*.R
|   |-- observers_*.R
|   |-- outputs_*.R
|   |- server_*.R
|   └- utils.R
|-- tests/
|-- vignettes/
|-- man/
|-- inst/
[...]

An optional app.R file can be added to the base directory of the package to launch the Shiny app using the shiny::runApp() or a main shiny app deployment function.

17.4 Testing

All plain non-reactive functions in the package should be testable using standard unit testing tools (e.g., testthat).

The use of # nocov start and # nocov end is allowed to ignore part of the code that cannot be tested using traditional unit tests (e.g., observeEvent).

For instance:

# nocov start
observeEvent(input$example_input, {
    # <do something>
})
# nocov end

Use files setup-*.R in the subdirectory tests/testthat/ to generate only once objects that are used repeatedly as input for the unit tests.

It is recommended to use the shinytest2 package to test visual and computational aspects of Shiny apps.

17.5 Documentation

Man pages documenting functions that return Shiny apps should use the interactive() function to demonstrate usage of the app.

For instance, a typical ‘Example’ section for such a man page should look as follows:

library(MyShinyPackage)

app <- build_app(...)

if (interactive()) {
  shiny::runApp(app, ...)
}

Although optional, we highly recommend documenting internal functions of packages that contain Shiny apps. We recommend doing so in a way that is visible to developers but not users:

  • If using roxygen2, use the tag @keywords internal in the Roxygen block. This will keep the documentation in the package but remove it from the help page index. You may want to increase the visibility of internal documentation by linking to it in other help pages within the package (e.g., within a “for developers” section).

For example:

#' @keywords internal
.build_app <- function(...) {
  ...
}
  • Alternatively (though less common), prefix the manual pages with INTERNAL_* and add them to man/.gitignore.

To set the name of an Rd document, use the @name tag in the Roxygen block:

#' @name INTERNAL_build_app
.build_app <- function(...) {
  ...
}

17.6 Review

When reviewing a shiny app package, the reviewer should check that the package follows the guidelines outlined in this document.

We highly recommend that reviewers use the shinytest2 package to test the visual and computational aspects of the Shiny app in addition to standard unit tests for plain functions.

Reviewers should also check that the package documentation is complete and accurate, and that the package passes BiocCheck in addition to R CMD build and check.

The user experience is very important. Reviewers should ensure that the app is responsive and that the user interface is intuitive and easy-to-use. The app should not crash or hang when the user interacts with it.

Errors and warnings should be handled gracefully and should be visible and intelligible to the user. We recommend using shinytoastr to display error, warning, and info messages to the user.