Introduction
shinystate
is an R package that enables Shiny
application developers to customize key features of bookmarkable
state. The bookmarkable state feature included in shiny
lets an application user save the state of the application (such as the
values of inputs), in which the state can either be encoded in a custom
URL or saved as objects to a hosting server. For a standard Shiny
application with a small number of input controls, the built-in
bookmarkable state features will likely suffice. However, a handful of
limitations exist for intermediate or complex applications:
- Encoding a large number of input settings using the URL method may reach or surpass the allowable length of a URL in web browsers.
- Saving bookmarkable state to a server requires a compatible hosting platform (such as Posit Connect or Shiny Server Pro). Even with those hosting providers, the bookmarkable state session files are saved to directories in the hosting server only accessible by system accounts, and not easily shared between users.
Shiny includes the ability to augment bookmarkable state with
callback functions as discussed in the advanced
bookmarking article, intended to assist with applications involving
a complex reactive structure alongside user inputs. On the surface,
these callbacks appear to address different issues than the
aforementioned limitations. In the inagural R/Pharma conference held in
2018, Joe Cheng shared a Shiny application
demonstrating small enhancements to managing multiple bookmarkable state
sessions for the current user in a development context. The
shinystate
package incorporates novel approaches to offer
Shiny developers an intuitive framework to address these
limitations:
- Integrate with the
pins
package to offer multiple storage locations to save bookmarkable state files. - Add optional metadata to compliment the existing bookmarkable state objects as the time a snapshot is created.
- Perform bookmarkable state operations using a new
R6
class calledStorageClass
.
Usage
To enable saving bookmarkable state with shinystate
, you
need to:
- Load the package:
library(shinystate)
- Create an instance of the
StorageClass
class outside of the application user interface and server functions:StorageClass$new()
- Include
use_shinystate()
in your UI definition - Call the
register_metadata()
method from your instance of theStorageClass
class at the beginning of the application server function - Enable the save-to-server bookmarking method by adding
enableBookmarking = 'server'
in the call toshinyApp()
- Call the
snapshot()
method from your instance of theStorageClass
class to save the state of the Shiny app session - Call the
restore()
method from your instance of theStorageClass
class to restore a saved session based on the session URL, available in the data frame returned from theget_sessions()
method.
Example Application
Below is an example application illustrating the default usage of
shinystate
. The application has a small set of user inputs
as well as a reactive value that will also be saved as part of the
bookmarkable state session. A pair of action buttons trigger the saving
and loading of bookmarkable state within their respective
observeEvent
expressions. In this application, the most
recent bookmarkable state session is restored by obtaining the last
record’s URL value in the session data frame.
library(shiny)
library(bslib)
library(shinystate)
storage <- StorageClass$new()
ui <- function(request) {
page_sidebar(
title = "Basic App",
sidebar = sidebar(
accordion(
open = c("user_inputs", "state"),
accordion_panel(
id = "user_inputs",
"User Inputs",
textInput(
"txt",
label = "Enter Title",
placeholder = "change this"
),
checkboxInput("caps", "Capitalize"),
sliderInput(
"bins",
label = "Number of bins",
min = 1,
max = 50,
value = 30
),
actionButton("add", "Add")
),
accordion_panel(
id = "state",
"Bookmark State",
actionButton("bookmark", "Bookmark"),
actionButton("restore", "Restore Last Bookmark")
)
)
),
use_shinystate(),
card(
card_header("App Output"),
plotOutput("distPlot")
)
)
}
server <- function(input, output, session) {
storage$register_metadata()
vals <- reactiveValues(sum = 0)
plot_title <- reactive({
if (!shiny::isTruthy(input$txt)) {
value <- "Default Title"
} else {
value <- input$txt
}
if (input$caps) {
value <- toupper(value)
}
return(value)
})
onBookmark(function(state) {
state$values$currentSum <- vals$sum
})
onRestore(function(state) {
vals$sum <- state$values$currentSum
})
observeEvent(input$add, {
vals$sum <- vals$sum + input$n
})
output$distPlot <- renderPlot({
req(plot_title())
x <- faithful$waiting
bins <- seq(min(x), max(x), length.out = input$bins + 1)
hist(
x,
breaks = bins,
col = "#007bc2",
border = "white",
xlab = "Waiting time to next eruption (in mins)",
main = plot_title()
)
})
observeEvent(input$bookmark, {
storage$snapshot()
showNotification("Session successfully saved")
})
observeEvent(input$restore, {
session_df <- storage$get_sessions()
storage$restore(tail(session_df$url, n = 1))
})
setBookmarkExclude(c("add", "bookmark", "restore"))
}
shinyApp(ui, server, enableBookmarking = "server")