Search code examples
rstrava

Creating a function for easier use of the Strava API from the rStrava package in R does not work but the code outside a function works fine


I work as an exercise physiologist, and I use the rStrava package for creating an API between R and my Strava account for doing session analyses. The API works fine on its own (outside a function), but inside a function it does not run. In the post, there is an example of the code outside and inside a function.

Expected behavior from the function is that it returns a data frame called by what I give to df_name, with a resolution of either "low", "medium" or "high" on the session ID, id, that I get from my Strava account.

Beneath is the standalone code (not in a function) that works. Note that I have edited out my personal information using #'s.

library(rStrava)
    
# Data from my personal Strava API
    
app_name <- "#"
app_client_id <- "#"
app_secret <- "#"
    
# The token that makes the connection to my Strava data
    
strava_token <- httr::config(token = strava_oauth(app_name,
                                                      app_client_id,
                                                      app_secret,
                                                      app_scope = "activity:read_all"))
    
# Accessing the data
    
my_acts <- get_activity_list(strava_token)
id <- {#} # This is the ID from Strava that is unique to a session
strava_data <- get_activity_streams(my_acts,
                                        strava_token,
                                        id = id,
                                        resolution = "high")

The problem is that if I wrap this into a function, the expected behaviour is not achieved. This is the code inside a function:

generate_strava_session_data <- function(id, 
                                         df_name,
                                         resolution){
                                         
  
  library(rStrava)
  library(tidyverse)
  
  # Data from my personal Strava API
  
  app_name <- "#"
  app_client_id <- "#"
  app_secret <- "#"
  
  # The token that makes the connection to my Strava data
  
  strava_token <- httr::config(token = strava_oauth(app_name,
                                                    app_client_id,
                                                    app_secret,
                                                    app_scope = "activity:read_all"))

# These five print lines were added to debug

  print(strava_token)
  print(id)
  print(resolution)
  print(.Last.error)
  print(.Last.warning)

  # Accessing the data  
  
  my_acts <- get_activity_list(strava_token)
  df_name <- get_activity_streams(my_acts,
                             strava_token,
                             id = id,
                             resolution = resolution)
  
}

The code makes the connection to Strava and I'm able to authorize the connection, but it then stops. The five print lines added for debugging all, and I was given the error that "Object "high" not found".

Any help would be greatly appreciated.


Solution

  • As noted in the comments, assignments in function are only visible in local environment, i.e. in the function itself. Same applies to objects you pass as function arguments -- altering those inside your function does not change object values in global environment. The return value of the function is the value of the last evaluated expression or explicit return() call, in other words your function should end with something like

      ...
      get_activity_streams(my_acts,
                           strava_token,
                           id = id,
                           resolution = resolution)
    }
    

    (no assignment) or

      ...
      return(df_name)
    }
    

    There's also <<- operator that would allow you to alter global environment objects from functions, but generally, one should try to avoid such side effects as much as possible, this also applies to loading libraries inside a function.

    Without knowing what happened next in your code, it's difficult to guess if you attempted to use functions return value or assumed that the object passed as a df_name argument would hold the dataset. A working example might look something like this:

    library(dplyr)
    library(rStrava)
    
    generate_strava_session_data <- function(resolution = "medium", id = NULL){
      if (file.exists('.httr-oauth')){
        # use cached token, if exists
        stoken <- httr::config(token = readRDS('.httr-oauth')[[1]])
      } else {
        # load secrets from environment variables, 
        # .Renviron file of current project is one convenient option to set those up
        stoken <- httr::config(
          token = strava_oauth(Sys.getenv("STRAVA_APP_NAME"), 
                               Sys.getenv("STRAVA_CLIENT_ID"), 
                               Sys.getenv("STRAVA_APP_SECRET"), 
                               app_scope="activity:read_all",
                               cache = TRUE))
      }
      # limit request to past 10 days
      my_acts <- get_activity_list(stoken, 
                                   after = Sys.Date() - as.difftime(10, units = "days"))
      streams_df <- get_activity_streams(my_acts,
                                         stoken = stoken,
                                         id = id,
                                         resolution = resolution)
      # function's return value is return() argument or
      # the value of the last evaluated expression; 
      # otherwise it just returns NULL
      return(streams_df)
    }
    
    generate_strava_session_data() %>% 
      summarise(dist = max(distance), 
                avg_hr = mean(heartrate), 
                move_ratio = sum(moving) / n(), 
                duration = max(time) %>% lubridate::seconds_to_period(),
                .by = id) %>% 
      select(-id)
    #>      dist  avg_hr move_ratio   duration
    #> 1  4.6950 124.610      0.999    29M 19S
    #> 2 37.2897 122.367      0.942 7H 12M 24S
    #> 3  3.5909 134.767      0.996    28M 27S
    

    Created on 2023-10-15 with reprex v2.0.2