3 Shiny

Shiny is an R package that allows you to easily create rich, interactive web apps. Shiny allows you to take your work in R and expose it via a web browser so that anyone can use it. Shiny makes you look awesome by making it easy to produce polished web apps with a minimum amount of pain (but there will be some pain).

3.1 Shiny App Structure

Shiny apps are composed of two main elements:

UI: This is where you define the layout and appearance of your app, including sliders, plots, tabs, etc.

The server: This is where you connect your UI components using logic behind the scenes to drive app behaviour.

These two elements are combined to render a shiny app by calling shiny::shinyApp(ui = ui, server = server).

3.2 UI

3.2.1 Setting up the UI

To create a shiny app you must create a UI. Traditionally, in shiny you do this using the functions shiny::fluidPage() or shiny::navbarPage(). The UI will contain elements such as calls to CSS stylings, overall UI design, inputs, and outputs.

3.2.2 Layouts

One of the main things you will set up in the UI is the overall layout and theme. Shiny gives you multiple tools to craft your layout including:

Structure of a basic app with sidebar

Figure 3.1: Structure of a basic app with sidebar

The structure underlying a simple multi-row app

Figure 3.2: The structure underlying a simple multi-row app

3.2.3 Themes

Beyond the layout, you can also set a theme for your app. Currently, the easiest way to do this is to use the bslib package and set the theme by calling bslib::bs_theme(). There are a number of preset themes you can use, but you are also free to customize your own theme using this function. Read more on themes here.

You can further tweak your app’s look by using custom CSS stylings and by incorporating html code into your UI call. Read more about that here.

3.2.4 UI Inputs

There are a number of inputs that you can use in shiny apps such as shiny::radioButtons(), shiny::selectInput(), shiny::actionButton(), and shiny::dateRangeInput(). These allow users to interact and dictate what appears in our app. Check out the all of the potential inputs here.

These functions all share a two main arguments:

  • inputID: This is the identifier used to connect the front end with the back end; if your UI has an input with ID “name”, the server function will access it with input$name.
  • label: This is used to create a human-readable label for the control (ie. “Select Geography”).

NOTE

The inputId has two constraints:

  • It must be a simple string that contains only letters, numbers, and underscores (no spaces, dashes, periods, or other special characters allowed!). Name it like you would name a variable in R.

  • It must be unique. If it’s not unique, you’ll have no way to refer to this control in your server function!


Read more!

3.2.5 UI Outputs

In the UI we can also specify the outputs that we’d like to include, for example plots, text, or tables; check out the cheatsheet for a list of Output() functions. In essence, these are ‘placeholders’, which will be filled in based on what we define in our server. Similar to inputs, we must also specify an outputID as the first argument.

Read more!

3.3 Server

On the server side, we build the logic that will power our app. Specifically, we need to fill our outputs and link them to the corresponding inputs if required. We do this by using a different suite of functions that will dictate what we fill our outputs with and using reactivity elements to link inputs to outputs.

3.3.1 Server Inputs

Inputs are essentially data sent from the browser (ie. UI) to the server, which can be accessed by calling input$inputID. Because these are dictated on the browser side, these values are not modifiable (ie. they are read-only) within the server.

The other important point about inputs is we inherently want to use them in a “reactive” context; in other words, we want to have logic that reacts to user input to drive behaviour on the backend to cause some kind of update to the frontend browser. More on this in section 3.3.3.

Read more!

3.3.2 Server Outputs

Similar to inputs we refer to outputs using their output IDs by calling output$outputID. However, as you may have guessed, instead of receiving inputs from the browser, we are providing outputs to fill in the UI “placeholders”. Outputs are always created using render functions, these may be wrapped in in reactive statements so that they update depending on inputs or they can be stand alone outputs that aren’t connected to any inputs.

Read more!

3.3.3 Reactivity

Reactivity is what brings everything together in an app. This is where we can use user inputs to update UI and outputs. There are many different types of reactive functions for different purposes; you can make UI and ouputs update in real time, when you click a button, or under other conditions! This is called declarative programming when you set certain constraints and let the program (the server logic) determine when to execute under those constraints. Interestingly, shiny operates lazily, in other words, it will only do the minimum work required to get the job done and only when it has to. We are basically telling shiny to:

“Ensure there is a sandwich in the refrigerator whenever I look inside of it”


The different reactive functions and how they operate.

Figure 3.3: The different reactive functions and how they operate.

Read more!

3.3.4 Execution order

Prepare yourself, this is where we begin to flip all of our coding knowledge on its head. Unlike regular scripts where we expect R to execute things orderly, line by line, shiny server logic doesn’t function this way.. Instead, shiny will only execute specific lines of the server when it has what it needs for that line. For example:

This:

will result in the same output as this:

TIP

It’s still recommended to have things in order to avoid confusion. But I think it’s an important thing to realize, because it highlights what is truly going on in the background and what makes shiny “lazy”.

3.4 Dynamic UI

We won’t dive too deeply into dynamic UI elements. They essentially function as typical UI inputs, however, they can be used to update the UI by other inputs in a reactive function. This is similar to how we can use inputs to update outputs, except in this case the output is a UI element.

Read more!

3.5 Basic example

3.5.1 Set up

First we need to do some prep before we build our app. Let’s load our libraries:

NOTE

This is also where we would load datasets, set up global variables, and add paths if we need them for the app. Because this app is super simple we will just load our libraries.

3.5.4 Putting it all together

Now we’re finally ready to deploy locally!

3.6 Deploying to shinyapps.io

At the BCCDC we have a shinyapps.io account that allows us to publish our apps to their servers. We can set these to be publically viewable, as well as private. Typically, privately deployed apps are for development purposes or previewing prior to public release. To deploy the app you just need to call two functions like so:

3.7 Packaging a Shiny App

Using package structure for a shiny app gets your toes into the water of package development. It’s a long way from a complete package, but it’s still useful because it activates new tools that make it easier to work with larger app and provides you with standard conventions that can be used across projects.

3.7.1 Put all R code in the R/ directory

Because we are going to be working in an app-package we need to create an R/ directory. This is where we will keep all of the core R code components that will build our app. As a reminder, in a package we are leveraging useful tools like devtools::load_all() (See section 2.3), which will load/run all of the code in the R/ directory.

3.7.2 Write a function that starts your app

As mentioned in section 3.1, we need three primary pieces to create an app:

Now, to begin converting our project to a package-app we must wrap these three things into a function:

NOTE

The function you create shouldn’t need any arguments, all that we will use this for is to easily run our app locally and set it up for easy deployment later by simply calling myApp().

3.7.3 Save your data to the data/ directory

We may have some datasets or lists that we use in our app that aren’t routinely updated. These are perfect candidates to convert into .rda files using the handy function usethis::use_data() that we talked about in section 2.9. These will automatically be stored in the data/ directory and can be easily called directly after running load_all().

TIP

Good candidates for us include things like crosswalk files for health geographies, age categories, and lists (domains, diseases, regions).

3.7.4 Create an inst/ directory

The inst/ directory is where we can store other raw datasets that are more subject to change, for example the data used to feed our apps that comes from external pipelines. There is no standard convention so you can name things as you please, but common usage includes folders named inst/extdata or inst/ext to store these datasets.

NOTE

To load data we need to use system.file() to point to our data’s location, as well as setting the package arugment to our project’s name.

Notice that our package-app automatically knows to look in our inst/ folder?

3.7.5 Create a www/ directory

This directory is where you can store some of the other non-script or data components to the app such as CSS stylings or images. There are no rules to this but a suggestion would be to have a www/css and www/images sub-directory scheme for your app.

3.7.6 Create a DESCRIPTION file

Because we are working in a package environment one of the critical components will be our DESCRIPTION file, which was discussed in section 2.4.

Normally when you deploy an app, the rsconnect package automatically figures out all of the packages your code uses. But because we are making a package-app with a DESCRIPTION file, it requires you to explicitly specify them. The easiest way to do this is to call usethis::use_package(). You’ll need to start with shiny and pkgload:

3.7.7 Deploying your app-package

One of the final pieces to setting up your app-package is the app.R script. This is what will be used when we go to deploy our app to the server and contains two simple but important lines of code:

WARNING

Although this is an R script, we DO NOT place this under our R/ directory. This would result in an infinite loop when running load_all() due to the load_all() function in the script! Therefore place this at the top-level of your project directory.

Read more!

3.7.8 Workflow

Putting your app code into the package structure unlocks a new workflow:

  • Re-load all code in the app with Cmd/Ctrl + Shift + L. This calls devtools::load_all() which automatically saves all open files, source()s every file in R/, loads all datasets in data/ then puts your cursor in the console.

  • Re-run the app with myApp().

Read more!

3.8 Additional Resources