Explanation: Application structure
Source:vignettes/explanation/application-structure.Rmd
application-structure.Rmd
Philosophy
Shiny comes with a powerful reactive programming model and a rich set of functions for creating UI widgets or custom HTML structure. These features make it possible to quickly build impressive, interactive applications, but they can also make your code harder to test and reuse.
To address this, we recommend to separate the code which must depend on Shiny from the logic which can be expressed without it. The division makes for more robust and maintainable apps in our experience. To support this separation, Rhino encourages a specific structure for the R sources of your application:
-
main.R
: The entry point to your application. -
logic
: Application code independent from Shiny. -
view
: Shiny modules and related code.
Logic
Code which uses reactivity or UI builder functions can be hard to test and reuse. With proper design it is possible to express most of your application logic using plain R functions and data structures (like lists, data frames).
Use the logic
directory for code which can be expressed
without Shiny.
View
The view
directory should contain code which describes
the user interface of your application. Use the functions defined in
logic
here and connect them using reactivity.
Structure your application using Shiny modules. A typical module can look something like this:
box::use(
shiny[moduleServer, NS, renderText, tagList, textInput, textOutput],
)
box::use(
app/logic/messages[hello_message],
)
#' @export
ui <- function(id) {
ns <- NS(id)
tagList(
textInput(ns("name"), "Name"),
textOutput(ns("message"))
)
}
#' @export
server <- function(id) {
moduleServer(id, function(input, output, session) {
output$message <- renderText(hello_message(input$name))
})
}
Minimal app.R
A Rhino application comes with a minimal app.R
:
# Rhino / shinyApp entrypoint. Do not edit.
rhino::app()
You should not edit this file and instead write your top-level code
in app/main.R
. The comment is also important: thanks to the
shinyApp
string, RStudio recognizes this file as a Shiny
application and shows the “Run” and “Publish” buttons.
Such a solution gives Rhino full control over app startup. Steps
performed by rhino::app()
include:
- Purge box cache, so the app can be reloaded without restarting R session.
- Configure logger (log level, log file).
- Configure static files.
- Load the main module / legacy entrypoint.
- Add head tags (favicon, CSS & JS).
One can wonder if we really need a separate main.R
file.
Couldn’t we just define the top-level ui
and
server
in app.R
and pass it to
rhino::app()
as arguments, much like with
shiny::shinyApp()
?
We employ this solution to enforce consistent use of box in the
entire application. A file loaded with box::use()
can only
load other modules/packages with box::use()
. On the other
hand, Shiny simply sources app.R
, so library()
and source()
could be used in this single file.
As the entire Rhino application is loaded with
box::use(app/main)
, all the sources must be properly
structured as box modules.