16  Debugging apps

Published

2024-09-04

Warning

The contents for section are under development. Thank you for your patience.

I’ve created the shinypak R package In an effort to make each section accessible and easy to follow:

Install shinypak using pak (or remotes):

# install.packages('pak')
pak::pak('mjfrigaard/shinypak')

Review the chapters in each section:

library(shinypak)
list_apps(regex = 'debug')
## # A tibble: 5 × 2
##   branch                   last_updated       
##   <chr>                    <dttm>             
## 1 15_debug-util-funs       2024-09-04 10:06:57
## 2 25.0_debug-error         2024-02-13 04:29:39
## 3 25.1_debug-selected_vars 2024-01-15 10:29:25
## 4 25.2_debug-var_inputs    2024-01-15 10:25:12
## 5 25.4_debug-print         2024-01-15 10:04:21

Launch the app:

launch(app = "23.1_debug-error")

Download the app:

get_app(app = "23.1_debug-error")

During regular development, Posit Workbench’s interactive debugger lets us inspect variables and expressions and execute the code line-by-line. In Shiny functions, the debugger lets us track the execution of reactive expressions and observers, which allows us to unravel reactivity-related issues that are often difficult to diagnose.

Launch app with the shinypak package:

launch('25.0_debug-error')

16.1 Debugging modules

The contents of your Shiny app-package can quickly become a complicated and intertwined combination of functions: utility, modules, UI, server, etc. I like to display the relationship between the functions with abstract syntax trees:1

For example, we know scatter_plot() is called from within the scatter plot display module function:

█─mod_scatter_display_server 
└─█─scatter_plot

And mod_scatter_display_server() is called within movies_server():

█─movies_server 
├─█─mod_scatter_display_server 
 └─█─scatter_plot 
└─█─mod_var_input_server 

Which is called from inside sap:

█─launch_app 
├─█─movies_ui 
 ├─█─mod_var_input_ui 
 └─█─mod_scatter_display_ui 
└─█─movies_server 
  ├─█─mod_scatter_display_server 
   └─█─scatter_plot 
  └─█─mod_var_input_server

I find these abstract folder trees helpful when I’m debugging or testing Shiny functions. I can use them to try and anticipate the application call stack (especially when I end up with multiple utility functions or nested modules).

We’ll add browser() and observe() in the movies_server() function to capture the behaviors of both modules:

Launch app with the shinypak package:

launch('25.1_debug-selected_vars')
movies_server <- function(input, output, session) {

    observe({
      browser()
    
      selected_vars <- mod_var_input_server("vars")

      mod_scatter_display_server("plot", var_inputs = selected_vars)
      
    })

}
1
Observer scope
2
Activate debugger

Then we’ll load the package and display the app in the Viewer pane (below the Console):


Ctrl/Cmd + Shift + L

ℹ Loading sap
launch_app(options = list(test.mode = FALSE), run = 'p')
ℹ shinyViewerType set to pane

The application launches, but browser() pauses the execution of the modules and activates the IDE’s debugger. This allows us to view the objects that are available in movies_server() before the variables are passed to the graph rendering functions:

(a) Debugger with call to browser() inside observe()
Figure 16.1: Note that the plot hasn’t rendered in the application yet because the call to observe(browser()) suspends the execution of any subsequent code

In the Source pane, we can see the call to browser() highlighted (Browse[1]> tells us the location in the browser() function).

(a) R/movies_server.R with observe(browser())
Figure 16.2: Because browser() was called inside observe(), the execution will pause, and we can interactively examine values

In the debugger, we want to confirm the returned values from the variable input module, selected_vars, which requires us to execute the next two lines of code:

Browse[1]> n
Browse[2]> n
(a) Execute the function line-by-line with n to create selected_vars
Figure 16.3: Click the Next icon twice to create selected_vars

Inside movies_server():

mod_var_input_server() collects the following values and returns a reactive list (selected_vars):

  • Three variable names
    • x, y, z
  • Graph aesthetics
    • alpha and size
  • An optional plot title
    • plot_title

When we inspect selected_vars in the debugger console (without parentheses) we see the method (i.e., the reactive list of inputs), and not the actual values:

Browse[2]> selected_vars
reactive({
    list(y = input$y, x = input$x,
         z = input$z, alpha = input$alpha, 
         size = input$size, 
         plot_title = input$plot_title)
})

If we check selected_vars() (with parentheses) in the debugger, we see this contains the values from the variable input module:

Browse[2]> selected_vars()
$y
[1] "audience_score"

$x
[1] "imdb_rating"

$z
[1] "mpaa_rating"

$alpha
[1] 0.5

$size
[1] 2

$plot_title
[1] ""

These two steps confirm that the UI values are being collected by the variable input module and stored in selected_vars, so the error must be coming from inside the scatter plot display module.

16.2 Module communication

Launch app with the shinypak package:

launch('25.2_debug-var_inputs')

We’ll repeat a similar process in mod_scatter_display_server(), but include the call to observe(browser()) after moduleServer(). Then we’ll load the package and run the application again:

mod_scatter_display_server <- function(id, var_inputs) {
  moduleServer(id, function(input, output, session) {
    
    observe({
      browser()
    
      # module code
      
      })

  })
}
1
Wrap browser() in observe() and place after the call to moduleServer()


Ctrl/Cmd + Shift + L

ℹ Loading sap
launch_app(options = list(test.mode = FALSE), run = 'p')

Inside the module, we want to confirm var_inputs() is being created correctly from the var_inputs object in movies_server().

selected_vars is the input for mod_scatter_display_server() (as var_inputs)

  • var_inputs is converted to the reactive inputs
    • inputs is passed to scatter_plot() inside renderPlot()
Browse[2]> var_inputs()
$y
[1] "audience_score"

$x
[1] "imdb_rating"

$z
[1] "mpaa_rating"

$alpha
[1] 0.5

$size
[1] 2

$plot_title
[1] ""

16.2.1 Verify variable inputs

Inside the scatter plot display module, the var_inputs argument is used to create a reactive input() object for the graph created by scatter_plot():

    inputs <- reactive({
      plot_title <- tools::toTitleCase(var_inputs()$plot_title)
        list(
          x = var_inputs()$x,
          y = var_inputs()$y,
          z = var_inputs()$z,
          alpha = var_inputs()$alpha,
          size = var_inputs()$size,
          plot_title = plot_title
        )
    })
1
Variable inputs (from selected_vars)
2
inputs() for scatter_plot()

Now that we’ve confirmed var_inputs() has been created, we’ll verify the values are passed correctly from var_inputs() to inputs() (which is used to create the scatter plot).

To do this, we’ll progress through the module function (using n in the debugger console or by clicking Next) until the inputs() reactive has been created,

(a) Progressing past inputs() tells us it’s been created
Figure 16.4: Use n in the debugger or click Next to progress through the function
Browse[2]> inputs()
$x
[1] "imdb_rating"

$y
[1] "audience_score"

$z
[1] "mpaa_rating"

$alpha
[1] 0.5

$size
[1] 2

$plot_title
[1] ""

These two steps have shown us 1) the modules are communicating properly, and 2) the scatter plot display module contains the list of reactive values needed to render the graph.

My approach to debugging

If an application is producing a bug (i.e., failing to render an output, producing an error in the Console, etc.), I’ll start by placing a call to browser() (wrapped in observe()) at the top-level UI/server functions, then procced ‘down’ into the modules.


  1. Create abstract syntax trees with the ast() function from the lobstr package.↩︎