1

I have an app where a large dataset is read in before the app starts. The app has separate ui and server files. So the UI is visible straightaway and the div for output plot remains empty. It sort of hangs for about 2-3 seconds as the data is read in. And then the plot is displayed. The rest of the app is fast enough and requires no progress bars. I would like to show some progress/indication that the data is being read in rather than just "freezing" for few seconds.

Here is a dummy example. The data is only read in once before the app loads. The data is used in ui as well as server.

library(shiny)

# read big file
#saveRDS(diamonds,"diamonds.Rds")
x <- readRDS("diamonds.Rds")

ui = fluidPage(
  titlePanel("Progress bar test"),
  selectInput("in_opts","Select",choices=colnames(x),selected=1),
  verbatimTextOutput("out_txt")
)

server=function(input,output,session) {
  output$out_txt <- renderPrint({
    Sys.sleep(3)
    head(x)
  })
}

shinyApp(ui,server)

enter image description here

I have tried using shinycssloaders. It generally works. It works well in this dummy example. But, it doesn't work for the "reading in file" part since that is outside the withSpinner() function.

library(shiny)
library(shinycssloaders)

# read big file
#saveRDS(diamonds,"diamonds.Rds")
x <- readRDS("diamonds.Rds")

ui = fluidPage(
  titlePanel("Progress bar test"),
  selectInput("in_opts","Select",choices=colnames(x),selected=1),
  shinycssloaders::withSpinner(verbatimTextOutput("out_txt"))
)

server=function(input,output,session) {
  output$out_txt <- renderPrint({
    Sys.sleep(3)
    head(x)
  })
}

shinyApp(ui,server)

enter image description here

Is there a way to display progress/indicator for the readRDS() step?

mindlessgreen
  • 11,059
  • 16
  • 68
  • 113
  • Are you aware of shinys built-in [progress indicators](https://shiny.rstudio.com/articles/progress.html) using a progress object? – ismirsehregal Feb 24 '22 at 11:06
  • My understanding is that they are only useful inside reactive functions. – mindlessgreen Feb 24 '22 at 11:24
  • 1
    Shiny's progress indicators can also be used outside of a reactive context. It's also possible to force `withSpinner()` to show up - see [this](https://stackoverflow.com/questions/70618250/how-to-add-a-spinner-before-a-selectizeinput-has-loaded-all-the-choices-shiny/70621460#70621460). What is your preferred approach? – ismirsehregal Feb 24 '22 at 11:43
  • However, the progress indicators need to be called in the server function and I guess you are aware, that you can't show anything before the app itself is launched. Accordingly if you'd like to display something while loading *global* data, you would need to run the loading function async in another R session. Is this what you are up to? – ismirsehregal Feb 24 '22 at 13:32
  • Thanks for the link. Well.. I would like to display a progress indicator when the app is reading in data. This happens before the server part even starts. I am considering just displaying a gif or so which will be removed once the data is read in.. – mindlessgreen Feb 24 '22 at 14:06
  • 1
    You can't show anything without the server being ready (except there is a proxy). It's part of the shiny app object just as the UI and R is single threaded. The only thing you could do is to stop blocking the app from starting by detaching the long running function into another process. This way the app can start right away and monitor the long running process - see e.g. library(callr) and it's function `r_bg()`. – ismirsehregal Feb 24 '22 at 14:47
  • I feared this might be the case. In fact, it would be great if something could be done external to the app itself. ie; Do not display the app at all and instead just show a loading indicator until the app is completely loaded/ready. Kind of like what happens on shinyapps.io. – mindlessgreen Feb 24 '22 at 14:53

1 Answers1

1

As mentioned in the comments we can run the long computation in a separate process. This can e.g. be done via library(future).

Once the future_promise returns its result, it is assigned to a global reactiveVal - therefore all shiny sessions started later don't have to wait.

library(shiny)
library(promises)
library(future)
library(datasets)
library(shinycssloaders)

plan(multisession)

globalrv <- reactiveVal(NULL)

future_promise({
  Sys.sleep(10) # your long running function
  iris
}) %...>%
  globalrv() %...!% # assign result to globalrv
  (function(e) {
    globalrv(NULL) # error handling needed?
    warning(e)
  })

ui = fluidPage(
  titlePanel("Progress bar test"),
  conditionalPanel("output.trigger == null", shinycssloaders::withSpinner(uiOutput("dummy"))),
  conditionalPanel("output.trigger != null", verbatimTextOutput("out_txt"))
)

server = function(input, output, session) {
  output$trigger <- eventReactive(globalrv(), {globalrv()})
  outputOptions(output, "trigger", suspendWhenHidden = FALSE)
  output$out_txt <- renderPrint({
    req(globalrv())
    head(globalrv())
  })
}

shinyApp(ui,server)

result

Using renderUI instead:

library(shiny)
library(promises)
library(future)
library(datasets)
library(shinycssloaders)

plan(multisession)

globalrv <- reactiveVal(NULL)

future_promise({
  Sys.sleep(10) # your long running function
  iris
}) %...>%
  globalrv() %...!% # assign result to globalrv
  (function(e) {
    globalrv(NULL) # error handling needed?
    warning(e)
  })

ui = fluidPage(
  titlePanel("Progress bar test"),
  uiOutput("spinner"),
  verbatimTextOutput("out_txt")
)

server = function(input, output, session) {
  output$spinner <- renderUI({
    if(is.null(globalrv())){
      shinycssloaders::withSpinner(uiOutput("dummy"))
    } else {
      NULL
    }
  })
  
  output$out_txt <- renderPrint({
    req(globalrv())
    head(globalrv())
  })
}

shinyApp(ui,server)
ismirsehregal
  • 30,045
  • 5
  • 31
  • 78
  • Interesting example! Thanks for this. Some of the details are beyond me. But one thing that concerns me is the use of reactive values. Since it cannot be used outside of a reactive context. The data that is read in initially is used across the app in ui as well as server. I have updated my example app with a dropdown. – mindlessgreen Feb 24 '22 at 19:33
  • @rmf you can use `reactiveValues` globally to share data across shiny sessions - nothing to worry about. See [this](https://shiny.rstudio.com/gallery/chat-room.html) for example ("Get code"). I'd suggest to simply test the code. – ismirsehregal Feb 24 '22 at 19:42
  • 1
    @rmf ok - I just read your edit - Using the data in the UI makes it difficult. You could transfer the UI parts using the data into the serrver part (`renderUI`) or use a proxy server (even another shiny app would work). I'll leave this answer here - might be useful for someone else as it detached the global loading from the UI start. – ismirsehregal Feb 24 '22 at 19:46
  • Apart from the reactive value issue, your example does answer many other questions. So, thanks a lot and please leave this answer here. – mindlessgreen Feb 24 '22 at 19:50