12  Debugging apps

Published

2024-11-22

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: 6 × 2
##   branch                   last_updated       
##   <chr>                    <dttm>             
## 1 10_debugger              2024-12-15 03:02:26
## 2 11_debug-print           2024-11-20 22:09:54
## 3 15_debug-util-funs       2024-09-04 10:06:57
## 4 25.1_debug-selected_vars 2024-01-15 10:29:25
## 5 25.2_debug-var_inputs    2024-01-15 10:25:12
## 6 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")

Debugging Shiny applications usually requires using an interactive debugger,

Capturing reactive values with reactiveValuesToList() and sending output to the UI. The benefits of print debugging is that it’s easy to implement, doesn’t require any special tools or setup, and it provides a direct view of variable states and program flow at specific points.

One of the best tried and tested methods of debugging is simply adding a cat() or print() call somewhere in your code to print variables or objects to the R console. This is a basic but effective way to track variable changes.

In mod_var_input:

    code("module reactive values"),
    verbatimTextOutput(outputId = ns("mod_vals"))
  )
1
Optional label
2
Include the ns() for the inputId
  output$mod_vals <- renderPrint({
    lobstr::tree(
      reactiveValuesToList(
        x = input,
        all.names = TRUE
      )
    )
  })
1
Collect reactive values in module
2
Print these values to the UI
3
Include all reactive objects

Load the package and run the app:


Ctrl/Cmd + Shift + L

ℹ Loading sap
launch_app(options = list(test.mode = FALSE), run = 'p')
(a) ‘Print’ in launch_app()
Figure 12.1: reactiveValuesToList() printed from mod_var_inputs

Now we can see the reactive values from our module in the application sidebar!

We can also use this ‘print’ method to explore reactive values at various locations in our application. For example, if we wanted to print the reactive values for multiple modules in an app, we can use these methods in the top level movies_ui() and movies_server() functions.

In the bslib portion of movies_ui():

In movies_server():

Load the package and run the app:


Ctrl/Cmd + Shift + L

ℹ Loading sap
launch_app(options = list(test.mode = FALSE), 
  run = 'p', bslib = TRUE)
(a) ‘Print’ in launch_app(bslib = TRUE)
Figure 12.2: reactiveValuesToList() printed from movies_ui() and movies_server()

Here we can see both levels of reactive values (from the module and the UI/server functions). The handy thing about this method is that the values change when we interact with the application:

(a) Changing values in launch_app(bslib = TRUE)
Figure 12.3: y and vars-y both update when the UI inputs change

Launch app with the shinypak package:

launch('25.0_debug-error')

12.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 12.4: 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 12.5: 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 12.6: 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.

12.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] ""

12.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 12.7: 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

The Shiny documentation also has a list of methods for debugging apps, and learning how to read call stacks (or a stacktrace) will help you debug your shiny app.2, 3


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

  2. Watch this video to learn about call stacks and abstract folder trees with lobstr.↩︎

  3. Stack traces are also covered in Advanced R, 2ed, Mastering Shiny, and in the Shiny documentation. I’ve summarized some tips on reading Shiny call stacks in the stack traces section on the Appendix.↩︎