How create a set interval module and pause/edit the interval

69 Views Asked by At

TLDR: how to create a timer object to refresh parts across shiny app, this should be a module I can import across the app to keep all things in sync with the ability to pause/edit the interval.

I'm writing a shiny app. In this app I have multiple parts that I want to be updated on an interval.

I'm looking for a way to create a timer object I can use to refresh different parts at a given interval and also a way to pause/change the interval of the reactiveTimer.

I came up with the idea of using a common timer object (setInterval) in a module (using rhino and box) which I import in other modules and use with shiny$observeEvent(timer()....

This is the simple timer all it does is trigger every 500ms. Now I want to add the ability to pause and edit the interval.

# Set up a common reactive timer to trigger every 500 ms
common_timer <- shiny$reactiveTimer(interval = 1500)

#' @export
timer <- function() {
    return(common_timer())
}

I tried a few different things like making an R6 class to handle the state of my timer (active/inactive); returns timer object or NULL. But I can't get anything to work properly. My R6 class works but when I pause the timer I can't get it to restart again.

Here is some of my code. Here is an example of how I use the timer for getting some data into my app.

box::use(shiny)

#' @export
live_data <- shiny$reactiveVal(NULL)

#' @export
date_interval <- shiny$reactiveValues(
    start_date = lubridate::as_datetime("2019-01-01"),
    end_date = lubridate::as_datetime("2019-01-28")
)

box::use(lubridate)

box::use(./common_timer[ timer ])
box::use(./modules/helpers)
box::use(./modules/cpp)

#' @export
call_data <- function(session, side_bar_options) {
    shiny$observeEvent(timer$get_timer, {
        currency <- shiny$isolate(side_bar_options$currency())

        dt <- helpers$read_market_data(
            file = "./data/kucoin_btc-usdt-hourly.csv",
            symbols = currency,
            from = date_interval$start_date,
            to = date_interval$end_date
        )

        live_data(dt)

        # bump the dates by 10 days
        date_interval$start_date <- date_interval$start_date + lubridate$days(1)
        date_interval$end_date <- date_interval$end_date + lubridate$days(1)
    })
}

And here is where I want to pause and then after add ability to edit the interval for the timer.

box::use(shiny)

#' @export
ui <- function(id, width = 2) {
    ns <- shiny$NS(id)
    shiny$sidebarPanel(
        shiny$tags$h4("Side bar"),
        shiny$selectInput(
            inputId = ns("select_currency"),
            label = "Currency:",
            choices = c("BTC/USDT", "XMR/BTC"),
            selected = "BTC/USDT"
        ),
        shiny$selectInput(
            inputId = ns("supplementary_plot"),
            label = "Supplementary Plot:",
            choices = c("RSI", "MACD", "Volume")
        ),
        shiny$actionButton(
            inputId = ns("toggle_timer"),
            label = "Start/Pause"
        ),
        width = width
    )
}

box::use(../logic/common_timer[ timer ])

#' @export
server <- function(id, live_data) {
    shiny$moduleServer(id, \(input, output, session) {
        ns <- session$ns

        shiny$observeEvent(input$toggle_timer, {
            timer$toggle_timer()
        })

        supplementary_plot <- shiny$reactive({
            input$supplementary_plot
        })

        currency <- shiny$reactive({
            input$select_currency
        })

        return(list(
            currency = currency,
            supplementary_plot = supplementary_plot
        ))
    })
}

Any help would be appreciated. Thank you very much.

1

There are 1 best solutions below

0
On

I made the fewest modifications possible to my original example to demonstrate such a thing; and crossposted the solution to posit community.

library(shiny)

usesTimerModUI <- function(id) {
  ns <- NS(id)
  tagList(
    div(style="border:1px solid black;",
    h4(ns("")),
    verbatimTextOutput(ns("display"))
  ))
}

usesTimerModServer <- function(id,source_timer) {
  moduleServer(
    id,
    function(input, output, session) {
      stopifnot(is.reactive(source_timer))
      
      output$display <- renderText({
        capture.output(req(source_timer()))
      })
    }
  )
}

ui <- fluidPage(
  numericInput("ms","millisec",
               value=1000,
               min=100,
               max=2000,
               step=100),
  checkboxInput("chk","on/off",value=TRUE),
  hr(),
  usesTimerModUI("m1"),
  br(),
  usesTimerModUI("m2")

)

# to see more decimal points
op <- options(digits.secs = 6)

server <- function(input, output, session) {
  
  mytimer <- reactive({
    if(!isTruthy(input$chk)){
      return(NULL)
    } else{
      invalidateLater(millis=req(input$ms))
    }
    Sys.time()
  })
  
  last_timer_value <- reactiveVal(NULL)
  
  observeEvent(mytimer(),
               {if(!identical(last_timer_value,
                              mytimer())){
                 if(isTruthy(mytimer()))
                   last_timer_value(mytimer())
               }})
  
  usesTimerModServer(id="m1",
                     source_timer = last_timer_value)
  usesTimerModServer(id="m2",
                     source_timer = last_timer_value)
  

}

shinyApp(ui, server)