Best Practices: Shiny Development

My workflow is like below:

  1. Plan out the type of app. For a lot of applications, sometimes a htmlwidget in Rmd will do, and covers more cases as time goes on and javascript gets cooler. This is preferable as then you can publish to any server, not needing Shiny. But if it needs more server side interaction, I look at using flexdashboard to keep Rmd elements, but have shiny embedded. If its a big app though, its easier to keep track using a ui.R or a HTML template. I have a HTML template based on Gentella which I have tweaked for a company template, or if a client wants something embedded in their website make a new HTML template with their CSS, or finally if none of those are appropriate go to using an ui.R file.
  2. I use a separate ui.R and server.R file, no global.R just source supporting scripts at the top or both ui.R and server.R - (source("functions.R")) No big reason why not to use global.R but I potentially change the functions.R into a small package later that supports modules.
  3. The server.R I template with:
library(shiny)
source("functions.R")

function(input, output, session){

}
  1. I then work on the ui.R until I have a UI I like. Add any boilerplate such as tracking and shinyjs if any of those features are useful (love the visible/non-visible flags it has). It can be really hard to debug a ui.R if you get a bracket or comma in the wrong place, so I try to comment the end of big UI tags such as fluidRows() etc. As I'm adding outputX elements that are named I start adding the renderX elements to server.R to keep track of the reactive names.

  2. ui.R Can be annoying when removing elements with trailing commas breaking stuff, so I try to put a br() or div() at the end of each so its easier to delete stuff. I'm also strict with code spacing, put one element per line and run it through CMD+I to auto-indent as often as possible. It can lead to very long lines :-/ Another tip is to use comments at top of major sections, and folding those comments sections in the RStudio interface for sections you are happy with.

  3. Once the ui.R looks reasonable, I first make sure I have a working version of the server side logic outside of shiny, as its a lot easier to debug it there. Then I look to make some reactives() and eventReactives() as needed. I typically have a raw data reactive, then a processed data reactive that uses req() to depend on the raw data and the inputs, then maybe a final plot reactive if used by lots of output elements.

  4. I try to ensure a req() or validate(need() is on every function, and always use NULL when data is invalid within functions.

  5. Then start building the renderOutputs(), ideally keeping the code within simple as other logic is outside. Start running the app locally and checking how they look in the ui.R, probably tweak some columns() and widths.

  6. I debug by making sure any reactive objects are assigned to normal R objects after the req() (raw_data <- raw_data()) which I find keeps things simple, and then use browser() and cat() and str() generously to check bits are doing what I expected them to do. The browser() stops the app right at the top of the function, and you can walk through errors and inspect state more easily.

  7. If there is anything I repeat a lot or use again, will look to convert it into a Shiny module, and put it in a package or a script to source via functions.R. I generally try to move all the reactives() to the top of the function, and outputs to the bottom with logic in-between.

  8. Its not over yet as sometimes when you deploy to the server there are issues, for instance if the versions of packages aren't available on shinyapps.io or your own server. For public apps shinyapps.io is great, for private I use a combo of containerit, Docker and googleComputeEngineR to deploy it to a VM (this workflow) For user authentication I use googleID, which is at the top of the ui.R and server.R before any data is fetched.

HTH - bit more involved than I initially meant to write!
Mark

24 Likes