Using ExtendedTask within a Shiny app as a package

Hi everyone! This is probably more a question around futures and package structure than ExtendedTask and Shiny but I'm making a Shiny app as a package and encountering an error when I try to use ExtendedTask in that future cannot find a function from the package the app is within so I can't library it in because that'd be self referential if that makes sense? I made a package called { testextended} with an app.R in the root directory and two files in the R folder by building on the Extended task example (all im doing here is adding x and y in a different file):

app.R

if (!interactive()) {
  sink(stderr(), type = "output")
  tryCatch({
    library(testextended)
  }, error = function(e) {
    devtools::load_all()
  })
} else {
  devtools::load_all()
}

# Increase max file size to 1GB
options(shiny.maxRequestSize=1000*1024^2)

# Launch the app
testextended::test_app(runApp = interactive())

R/func.R

custom_function <- function(x, y) {
  x + y
}

R/test_app.R

#' @import shiny
#' @import future
#' @import promises
#' @import bslib
#' @export
test_app <- function(user = NULL, box_token = NULL, meta_yaml_id = NULL, runApp = TRUE) {

  future::plan(future::multisession)

  app <- shiny::shinyApp(
    ui = {
      fluidPage(
        numericInput("x", "x", value = 1),
        numericInput("y", "y", value = 2),
        actionButton("btn", "Add numbers"),
        textOutput("sum")
      )
    },
    server = function(input, output, session) {

      sum_values <- ExtendedTask$new(function(x, y) {
        future_promise({
          custom_function(x, y)
        })
      })

      observeEvent(input$btn, {
        sum_values$invoke(input$x, input$y)
      })

      output$sum <- renderText({
        sum_values$result()
      })

    }
  )

  # run build app
  if (runApp)
    runApp(app)
  else
    app

}

I currently get the error: Warning: Error in : there is no package called ‘testextended’ but since I'm IN that package I'm hesitant to use a library call inside the future call?

Anyone have any advice here?

Have you tried passing the function explicitly via future_promise's globals parameter?

Hi @ismirsehregal thanks for the suggestion! I tried like the code below but still get the same error? Do I need to leverage the packages argument or maybe envir? Is there something else I'm missing?

sum_values <- ExtendedTask$new(function(x, y) {
     future::future(
          { custom_function(x, y) },
          seed = 3,
          globals = list(custom_function = testextended::custom_function),
    )
})

I'd try to source("func.R", local = TRUE) inside the shiny app and pass the customfunction without referring to the package namespace (testextended::)

This should work also without passing the globals parameter. However, I currently can't test it.

Thank you for this! It does seem to work but I'm curious why I need to explicitly source the function to have this work when all other code/functions reliant on other functions are able to utilize the { testextended } package namespace? Is this best practices to be sourcing a file inside a package?

Code that works below:

#' @import shiny
#' @import future
#' @import promises
#' @import bslib
#' @export
test_app <- function(user = NULL, box_token = NULL, meta_yaml_id = NULL, runApp = TRUE) {

  future::plan(future::multisession)
  # Explicitly source the func.R code
  source("R/func.R", local = TRUE)

  app <- shiny::shinyApp(
    ui = {
      fluidPage(
        numericInput("x", "x", value = 1),
        numericInput("y", "y", value = 2),
        actionButton("btn", "Add numbers"),
        textOutput("sum")
      )
    },
    server = function(input, output, session) {

      sum_values <- ExtendedTask$new(function(x, y) {

        promises::future_promise(
          { custom_function(x, y) },
          seed = 3,
          globals = c("custom_function", "x", "y")
        )

      })

      observeEvent(input$btn, {
        sum_values$invoke(input$x, input$y)
      })

      output$sum <- renderText({
        sum_values$result()
      })

    }
  )

  # Run the app
  if (runApp)
    runApp(app)
  else
    app
}

I'm not sure regarding the best practice for package development in this context. However, you have to somehow, make the custom function available for the "fresh" R process running the future, once the future function fails to collect it on its own. I guess this is the issue. The custom function resides in an environment which isn't looked up or isn't available for the future (all based on assumptions as I havn't run your code).

I've use ExtendedTask inside a packaged app without needing to make explicit calls to the package, but the architecture is a bit different to yours. Everything app-related is in inst/shiny and server.R contains library(package). There's an example here: shinyscholar/inst/shiny/modules/select_async.R at master · simon-smart88/shinyscholar · GitHub