My workflow is like below:
- 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 aui.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 anui.R
file. - I use a separate
ui.R
andserver.R
file, noglobal.R
just source supporting scripts at the top or bothui.R
andserver.R
- (source("functions.R")
) No big reason why not to useglobal.R
but I potentially change thefunctions.R
into a small package later that supports modules. - The
server.R
I template with:
library(shiny)
source("functions.R")
function(input, output, session){
}
-
I then work on the
ui.R
until I have a UI I like. Add any boilerplate such as tracking andshinyjs
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 asfluidRows()
etc. As I'm addingoutputX
elements that are named I start adding therenderX
elements toserver.R
to keep track of the reactive names. -
ui.R
Can be annoying when removing elements with trailing commas breaking stuff, so I try to put abr()
ordiv()
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 throughCMD+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. -
Once the
ui.R
looks reasonable, I first make sure I have a working version of the server side logic outside ofshiny
, as its a lot easier to debug it there. Then I look to make somereactives()
andeventReactives()
as needed. I typically have a raw data reactive, then a processed data reactive that usesreq()
to depend on the raw data and the inputs, then maybe a final plot reactive if used by lots of output elements. -
I try to ensure a
req()
orvalidate(need()
is on every function, and always useNULL
when data is invalid within functions. -
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 theui.R
, probably tweak somecolumns()
and widths. -
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 usebrowser()
andcat()
andstr()
generously to check bits are doing what I expected them to do. Thebrowser()
stops the app right at the top of the function, and you can walk through errors and inspect state more easily. -
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 thereactives()
to the top of the function, and outputs to the bottom with logic in-between. -
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 appsshinyapps.io
is great, for private I use a combo ofcontainerit
, Docker andgoogleComputeEngineR
to deploy it to a VM (this workflow) For user authentication I usegoogleID
, which is at the top of theui.R
andserver.R
before any data is fetched.
HTH - bit more involved than I initially meant to write!
Mark