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 namedobservers_*.R
. Do this both for observers reacting to the Shiny serverinput
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 namedinterface_*.R
. - Place internal functions that update the Shiny server
output
object in files namedoutputs_*.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 toman/.gitignore
.
To set the name of an Rd document, use the @name
tag in the Roxygen block:
#' @name INTERNAL_build_app
.build_app <- function(...) {
...
}
Additionally, the vignette of a shiny app package might include some screenshots, either generated manually or programmatically.
If generated manually, developers can use tools such as pngquant (available as a command line utility and as a GUI on most systems) to reduce their size (about 70%) without too much loss of quality.
Programmatically generated screenshots can be realized by means of the Webshot2 package, using the webshot2::appshot()
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.