Server functions
{spsComps} has some useful functions for exception catch, expression validation, and more. Even though we say they are Shiny server functions, but in fact most of them can be run without a Shiny server. We have designed the functions to detect whether there is a Shiny server, if not, they will work only in R console as well.
load package
library(spsComps)
## Loading required package: shiny
library(magrittr)
Server components
shinyCatch
basic
The shinyCatch
function is useful to capture exception. What we mean exception
can be message
, warning
or error
. For example
shinyCatch({
message("This is a message")
warning("This is a warning")
stop("This is an error")
})
## [SPS-INFO] 2021-12-15 01:36:28 This is a message
##
## [SPS-WARNING] 2021-12-15 01:36:28 This is a warning
## [SPS-ERROR] 2021-12-15 01:36:28 This is an error
## NULL
You can see all 3 levels are captured inside the [SPS-XX] log on your console. If you run this in your Shiny app, a pop-up message with the corresponding log level message will be displayed in in app, like following:
So the message on both UI and console is called dual-end logging in SPS.
Shiny off
Of course, if you do not want users to see the message, you can hide it by
shiny = FALSE
, but the message will be still logged on R console.
Run the following on your own computer and watch the difference.
library(shiny)
ui <- fluidPage(
spsDepend("toastr")
)
server <- function(input, output, session) {
shinyCatch({
stop("This is an error")
}, shiny = FALSE)
}
shinyApp(ui, server)
get return
shinyCatch
is able to return you values if your expression has any. Imagine we
have a function addNum
that gives message, warning or error depeend on the input.
addNum <- function(num){
if (num > 0) {message(num)}
else if (num == 0) {warning("Num is 0")}
else {stop("less than 0")}
return(num + num)
}
value_a <- shinyCatch({
addNum(1)
})
## [SPS-INFO] 2021-12-15 01:36:28 1
value_a
## [1] 2
value_b <- shinyCatch({
addNum(0)
})
## [SPS-WARNING] 2021-12-15 01:36:28 Num is 0
value_b
## [1] 0
value_c <- shinyCatch({
addNum(-1)
})
## [SPS-ERROR] 2021-12-15 01:36:28 less than 0
value_c
## NULL
You can see at message
and warning
level, the expected value returned, and
at error
level, the return is NULL
. So the following code value_c
still runs
and is not blocked by the error
occurred in shinyCatch
.
Blocking level
More often, if there is an error, we do want take the log in R console, inform the Shinyapp user
and then stop the following code. In this case, we need to specify the blocking_level
. So,
default is "none"
, do not block and return NULL
if there is an error, and you can
choose "error"
, "warning"
or "message"
.
- error: block downstream if the first
error
detected inshinyCatch
- warning: block downstream if the first
error
orwarning
detected inshinyCatch
- message: block downstream if the first
error
,warning
ormessage
detected inshinyCatch
You can see the stringency becomes tighter: message > warning > error
Blocking code will generate error, in order to have the Rmd rendered, we wrap the expression in try
try({
shinyCatch({
stop("error level is the most commonly used level")
}, blocking_level = "error")
print("This will not be evaluated")
})
## [SPS-ERROR] 2021-12-15 01:36:28 error level is the most commonly used level
## Error :
try({
shinyCatch({
message("error level is the most commonly used level")
}, blocking_level = "message")
print("This will not be evaluated either")
})
## [SPS-INFO] 2021-12-15 01:36:28 error level is the most commonly used level
##
## Error :
You can see the following print
in both cases are not got evaluated.
Block reative
The most useful case for shinyCatch
is to use it in the Shiny reactive
context. Most errors in shiny::reactive, shiny::observer, shiny::observeEvent,
or shiny::renderXXX series function will crash the app. With shinyCatch
, it
will not. It “dual-logs” the error and stop downstream code.
The following example use shiny::reactiveConsole()
to mock a Shiny server session
shiny::reactiveConsole(TRUE)
y <- observe({
stop("an error from a function")
print("some other process")
})
## Warning: Error in <observer>: The value of x is
## 38: stop
## 37: <observer> [#2]
## 35: contextFunc
## 34: env$runWith
## 27: ctx$run
## 26: run
## 7: flushCallback
## 6: FUN
## 5: lapply
## 4: ctx$executeFlushCallbacks
## 3: .getReactiveEnvironment()$flush
## 2: flushReact
## 1: <Anonymous>
It crashes the app. However, if you use shinyCatch
shiny::reactiveConsole(TRUE)
y <- observe({
shinyCatch({
stop("an error from a function")
}, blocking_level = "error")
print("some other process")
})
## [SPS-ERROR] 2021-03-02 22:13:05 an error from a function
It only logs the error and prevent the downstream print
to run. Now try following
real Shiny apps and watch the difference:
# with shinyCatch
library(shiny)
server <- function(input, output, session) {
observe({
shinyCatch({
stop("an error from a function")
}, blocking_level = "error")
print("some other process")
})
}
shinyApp(fluidPage(spsDepend("toastr")), server)
# without shinyCatch
library(shiny)
server <- function(input, output, session) {
observe({
stop("an error from a function")
print("some other process")
})
}
shinyApp(fluidPage(spsDepend("toastr")), server)
spsValidate
In data analysis, it is important we do some validations before the downstream
process, like make a plot. It is epecially the case in Shiny apps. We cannot
predict what the user inputs will be, like what kind of data they will use.
Similar to shinyCatch
, spsValidate
is able to catch exceptions but more
useful to handle validations. In addtion to shinyCatch
functionalities, it
will give users a success message if the expression goes through and return TRUE
(shinyCatch
returns the final expression value).
shiny::reactiveConsole(TRUE)
x <- reactiveVal(10)
y <- observe({
spsValidate({
# have multiple validations in one expression
if (x() == 1) stop("cannot be 1")
if (x() == 0) stop("cannot be 0")
if (x() < 0) stop("less than 0")
})
message("The value of x is ", x())
})
x(0)
x(-10)
## The value of x is 10
## [ ERROR] 2021-03-02 22:36:16 cannot be 0
## [ ERROR] 2021-03-02 22:36:16 less than 0
Try this real Shiny app:
library(shiny)
ui <- fluidPage(
spsDepend("toastr"),
shiny::sliderInput(
"num", "change number",
min = -1, max = 2, value = 2, step = 1
)
)
server <- function(input, output, session) {
x <- reactive(as.numeric(input$num))
y <- observe({
spsValidate(vd_name = "check numbers", verbose = TRUE, {
# have multiple validations in one expression
if (x() == 1) stop("cannot be 1")
if (x() == 0) stop("cannot be 0")
if (x() < 0) stop("less than 0")
})
message("The value of x is ", x())
})
}
shinyApp(ui, server)
You should see the success message like this:
shinyCheckPkg
Sometimes we want the app behave differently if users have certain packages installed.
For example, if some packages are installed, we open up additional tabs on
UI to allow more features. This can be done with the shinyCheckPkg
function.
This function has to run inside Shiny server, an alternative version to use
without Shiny is from the spsUtil package, spsUtil::checkNameSpace
Use it in Shiny server, specify the packages you want to check by different sources, like CRAN, Bioconductor, or github.
shinyCheckPkg(session, cran_pkg = c("pkg1", "pkg2"), bioc_pkg = "bioxxx", github = "user1/pkg1")
It will return TRUE
if all packages are installed, otherwise FALSE
Now try this real example. We check if the ggplot99
package is installed, if
yes we make a plot. It also combines the spsValidate
function. You can have
a better idea how these functions work.
library(shiny)
ui <- fluidPage(
tags$label('Check if package "pkg1", "pkg2", "bioxxx",
github package "user1/pkg1" are installed'), br(),
actionButton("check_random_pkg", "check random_pkg"),
br(), spsHr(),
tags$label('We can combine `spsValidate` to block server code to prevent
crash if some packages are not installed.'), br(),
tags$label('If "shiny" is installed, make a plot.'), br(),
actionButton("check_shiny", "check shiny"), br(),
tags$label('If "ggplot99" is installed, make a plot.'), br(),
actionButton("check_gg99", "check ggplot99"), br(),
plotOutput("plot_pkg")
)
server <- function(input, output, session) {
observeEvent(input$check_random_pkg, {
shinyCheckPkg(session, cran_pkg = c("pkg1", "pkg2"), bioc_pkg = "bioxxx", github = "user1/pkg1")
})
observeEvent(input$check_shiny, {
spsValidate(verbose = FALSE, {
if(!shinyCheckPkg(session, cran_pkg = c("shiny"))) stop("Install packages")
})
output$plot_pkg <- renderPlot(plot(1))
})
observeEvent(input$check_gg99, {
spsValidate({
if(!shinyCheckPkg(session, cran_pkg = c("ggplot99"))) stop("Install packages")
})
output$plot_pkg <- renderPlot(plot(99))
})
}
shinyApp(ui, server)
You should see something like this if there is any missing package:
In-line operation
In-place operations like i += 1, i -= 1 is not support in R. These functions implement these operations in R. This set of functions will apply this kind of operations on [shiny::reactiveVal
] objects.
reactiveConsole(TRUE)
rv <- reactiveVal(0)
incRv(rv) # add 1
rv()
## [1] 1
incRv(rv) # add 1
rv()
## [1] 2
incRv(rv, -1) # minus 1
rv()
## [1] 1
incRv(rv, -1) # minus 1
rv()
## [1] 0
rv2 <- reactiveVal(1)
multRv(rv2) # times 2
rv2()
## [1] 2
multRv(rv2) # times 2
rv2()
## [1] 4
diviRv(rv2) # divide 2
rv2()
## [1] 2
diviRv(rv2) # divide 2
rv2()
## [1] 1
reactiveConsole(FALSE)
If you are looking for inline operations on normal R objects or
shiny::reactiveValues
, check spsUtil