Skip to contents

Overview

shinybreakpoint is designed to display in the Shiny app (i.e. when the app runs) parts of the source code used in the app and to allow to set breakpoint in some places of the displayed source code. Thus it should be clear from the beginning that this package has two-stage limitations:

  • breakpoint can’t be set on the lines which are not displayed
  • breakpoint won’t be set on all lines which are displayed

Setting breakpoint is a debugging technique - when the point (line of code) is reached, code execution is halted, debug mode is enabled and one can check the values of objects or perform any other operations in the temporary environment (i.e. all changes exist only in the debug mode). It is one of the method to find out why unexpected behavior occurred.

shinybreakpoint do not provide new technique for Shiny apps - it is already possible to set breakpoint using e.g. RStudio IDE, but the current solution has its own limitation. Breakpoint can’t be set when the code is split into multiple files (which is often the case when the app is built of modules). Although shinybreakpoint gives the solution for that, it makes it in the radical way - the code has to be split into (at least one), separated from server part, function.

Minimal App Structure

We will start from the snippet1 - minimal skeleton needed to successfully run the Shiny app with the shinybreakpoint functionality:

library(shiny)

ui <- fluidPage(
  
)

appServer <- function(input, output, session) {
  # here will be the code which will be run in the
  # 'server', not in the 'server' itself
}

server <- function(input, output, session) {
  appServer(input, output, session)
  shinybreakpoint::shinybreakpointServer()
}

shinyApp(ui, server)

it is very similar to the regular Shiny snippet, however with the one significant difference - the server part is duplicated.

This duplication is necessary, because when the breakpoint is set using shinybreakpoint, the session is refreshed to enable changes in the code (these changes are the code added to the chosen line, i.a. browser()). Refreshment works only for objects nested in the functions which are then call in the server part. In other words, breakpoint won’t work for objects used directly in the server, but these objects will be visible in the source code. As an example that works, we can consider the code below.

library(shiny)
library(magrittr)

ui <- fluidPage(
  numericInput("num1", "Num", 1),
  numericInput("num2", "Num", 2),
  actionButton("go", "Go")
)

appServer <- function(input, output, session) {
  observe({
    input$num1
  })
  
  observe({
    input$num2
  }) %>% 
    bindEvent(input$go)
}

server <- function(input, output, session) {
  appServer(input, output, session)
  shinybreakpoint::shinybreakpointServer()
}

shinyApp(ui, server)

The key concept is that all code on which we possibly would like to set breakpoint is separated from the server (shinybreakpoint::shinybreakpointServer() does not necessary need to be use in the server, it could be used in the appServer as well). When the app is built of modules, this separation is achieved by definition, but when not, then shinybreakpoint needs this additional step.

Duplicated main server part of the app is necessary if one would like to set brekapoint in the main server, but shinybreakpoint, being a shiny module, was developed having Bootstrap 5 in mind, which means that the expected appearance of the module will be noticeable with the bslib::bs_theme(5) set as theme - this code sets Bootstrap version 5 for the app.

The second version of shinybreakpoint brought also a new functionality - filtering displayed source code by Id - this is Id of intput or output element (more about this, reader can find in the article Filtering by Id). Filtering is based on reactlog and to be able to use it, it is needed to set the appropriate option. This is not only enabling reactlog, but also setting up some additional function responsible to manage temporary files, thus it is needed to use shinybreakpoint::set_filtering_by_id(), not just e.g. options(shiny.reactlog = TRUE). The snipped below shows the recommended structure of the app - changes concerns only on the main app file. In this code snippet, two TODOs was included as a reminder to remove these calls before app is send to production.

    library(shiny)

    shinybreakpoint::set_filtering_by_id() # TODO: remove

    ui <- fluidPage(
      theme = bslib::bs_theme(5),
    )

    appServer <- function(input, output, session) {
      # here will be the code which will be run in the
      # 'server', not in the 'server' itself
    }

    server <- function(input, output, session) {
      appServer(input, output, session)
      shinybreakpoint::shinybreakpointServer() # TODO: remove
    }

    shinyApp(ui, server)

Setting breakpoint

To run any of the examples above, one should save the file with the code, run the app and press F4 (as this is key used by default, check out the other parameters by running ?shinybreakpoint::shinybreakpointServer in the console) - the modal dialog will pop up. In our example there are two numeric inputs and one button in the UI as well as two observes (one is eager - will run immediately and one will run only after the button is pushed) in the server. observes will be visible in the modal dialog and breakpoint can be set on two lines:

  • input$num1 and
  • input$num2

other possibilities won’t work. Generally we can say that breakpoint won’t be set on the edges of the visible code blocks.

Immediately after the breakpoint is set (i.e. when the red filled circle button () in the modal dialog is pushed), session is refreshed. That means that if the breakpoint in our example was set on the input$num1 line, debug mode will open up immediately, but if it was set on the input$num2, then Go button needs to be pushed. That fact strictly depends on the reactive programming - debug mode opens up when the code block (where breakpoint is set) starts and the chosen line is achieved.

Displaying source code

shinybreakpoint tries to find and display only objects which belong to reactive context (observes, reactives and render*s), however it is not guaranteed that all of these objects will be find and that only these objects will be find. If so, one should remember that shinybreakpoint was designed to work with objects which belong to reactive context and thus it can lead to errors if using on other objects. It is also crucial to note that reactive context will be displayed only if the body (of this context) is inside curly ({}) brackets, i.e. reactive({iris}) should be use instead of reactive(iris). But - of course, if one would like to set breakpoint inside the reactive context, it has to be a space for this, i.e. in our example the code should look like below (body of function is in the separate line):

r_iris <- reactive({
  iris
})

Any element of reactive context must also be inside the server part (in the main app or in the module). Theoretically it is possible to include reactive context (e.g. observe()) outside of the server part (and of course outside of the UI part) if one would like to share something between Shiny sessions, but shinybreakpoint currently won’t display such a code.

Additional Comments

Not previously mentioned and possibly important remarks are:

  • debugging is not performed on the original file, but on the temporary copy of the file
  • breakpoint is designed to be an one-time breakpoint and it means that breakpoint do not persist and only one breakpoint can be set in one time
  • when the browser() and other code is added, the srcref attribute of function is lost. However, it shouldn’t be a problem since this attribute is used to get information about the source of object and therefore is useful only for debugging
  • session is reload twice - first time after the breakpoint is set and the second one when debug mode is closed using c or f. This can be inconvenient if some heavy computations are performed when the session starts
  • if one uses RStudio IDE, shinybreakpoint recognizes the file opened in the source editor and if this file is used in Shiny app and contains reactive blocks of code, it will be opened as default file in modal dialog. This works only in a moment when the key specified in keyEvent parameter is pressed