You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建可暂停/编辑的Shiny全局定时刷新模块

Solution: Reusable R6 Timer Module for Shiny (Rhino/Box)

Step 1: Create the R6 Timer Module

Replace your existing common_timer module with this R6-based implementation, which handles pause/resume and interval updates correctly:

box::use(shiny)
box::use(R6[R6Class])

#' @export
CommonTimer <- R6Class(
  "CommonTimer",
  public = list(
    #' @field trigger Reactive value that increments on each timer tick
    trigger = NULL,
    
    #' @description Initialize the timer
    #' @param interval Initial interval in milliseconds
    initialize = function(interval = 1500) {
      private$interval <- interval
      self$trigger <- shiny$reactiveVal(0)
      private$active <- FALSE
    },
    
    #' @description Toggle timer on/off
    toggle_timer = function() {
      private$active <- !private$active
      if (private$active) {
        # Start new timer and observer to update trigger
        private$timer <- shiny$reactiveTimer(private$interval)
        private$observer <- shiny$observe({
          private$timer()
          self$trigger(self$trigger() + 1)
        })
      } else {
        # Clean up timer and observer when pausing
        if (!is.null(private$observer)) {
          private$observer$destroy()
          private$observer <- NULL
        }
        if (!is.null(private$timer)) {
          private$timer$destroy()
          private$timer <- NULL
        }
      }
    },
    
    #' @description Update timer interval
    #' @param new_interval New interval in milliseconds
    set_interval = function(new_interval) {
      private$interval <- new_interval
      # Restart timer if active to apply new interval
      if (private$active) {
        self$toggle_timer()
        self$toggle_timer()
      }
    },
    
    #' @description Check if timer is active
    #' @return Logical indicating active state
    is_active = function() {
      private$active
    }
  ),
  private = list(
    interval = NULL,
    active = FALSE,
    timer = NULL,
    observer = NULL
  )
)

#' @export
# Singleton instance for app-wide use
common_timer <- CommonTimer$new(interval = 1500)

Step 2: Update Data Call Module

Modify your call_data function to listen to the timer's reactive trigger:

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[ common_timer ])  # Import the singleton timer
box::use(./modules/helpers)
box::use(./modules/cpp)

#' @export
call_data <- function(session, side_bar_options) {
    shiny$observeEvent(common_timer$trigger(), {
        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)

        # Increment date range
        date_interval$start_date <- date_interval$start_date + lubridate$days(1)
        date_interval$end_date <- date_interval$end_date + lubridate$days(1)
    })
}

Step 3: Enhance Sidebar Module for Timer Control

Update the sidebar UI to include interval adjustment, and modify the server to interact with 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")
        ),
        # Add interval input
        shiny$numericInput(
            inputId = ns("interval"),
            label = "Refresh Interval (ms):",
            value = 1500,
            min = 500,
            max = 10000,
            step = 100
        ),
        shiny$actionButton(
            inputId = ns("toggle_timer"),
            label = "Start"
        ),
        width = width
    )
}
box::use(shiny)

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

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

        # Toggle timer on button click
        shiny$observeEvent(input$toggle_timer, {
            common_timer$toggle_timer()
            # Update button label based on state
            shiny$updateActionButton(
                session,
                "toggle_timer",
                label = if (common_timer$is_active()) "Pause" else "Start"
            )
        })

        # Update timer interval when input changes
        shiny$observeEvent(input$interval, {
            common_timer$set_interval(input$interval)
        })

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

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

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

Key Fixes & Explanations

  • R6 State Management: The R6 class encapsulates timer state (active/inactive, interval) and handles cleanup of observers/timers when pausing. This avoids stale reactiveTimer instances that can't be restarted.
  • Reactive Trigger: The trigger reactiveVal acts as a single source of truth for all components to listen to, ensuring synchronized refreshes.
  • Cleanup: When pausing, we explicitly destroy the reactiveTimer and observer to free resources, which was missing in your initial R6 attempt.
  • Interval Updates: Changing the interval restarts the timer if it's active, ensuring the new interval takes effect immediately.

This implementation works seamlessly with Rhino and Box, providing a reusable, app-wide timer with full control over pause/resume and interval adjustments.

内容的提问来源于stack exchange,提问作者dereckmezquita

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 11:44:54