---
title: "Example: Spotify login to display listening data"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Example: Spotify login to display listening data}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

## Overview

This example lets a user connect their Spotify account and displays their
top tracks. It uses the same provider, client, and module setup as
[Usage](usage.html), then calls Spotify's API with the access token.
Spotify supplies profile data through its API rather than an OIDC ID token.

The helper identifies accounts using immutable `account_id`, as specified in
[Spotify's May 2026 changes](https://developer.spotify.com/documentation/web-api/references/changes/may-2026).
Before upgrading an existing app, migrate stored `id` mappings and audit digests
using both identifiers from an authenticated profile. Do not merge accounts by
email or display name. `allow_legacy_id = TRUE` permits a temporary fallback only
when `account_id` is missing; it does not preserve old mappings when both exist.

## Spotify app registration

1. Create an app in the [Spotify developer dashboard](https://developer.spotify.com/dashboard).
2. Register `http://127.0.0.1:8100` as its redirect URI. Spotify accepts a
   loopback IP address for local HTTP development, but not `localhost`.
   See [Spotify's redirect URI rules](https://developer.spotify.com/documentation/web-api/concepts/redirect_uri).
3. Store the credentials as `SPOTIFY_OAUTH_CLIENT_ID` and
   `SPOTIFY_OAUTH_CLIENT_SECRET` in your R environment. See the
   [environment setup example](usage.html#github-app-registration) for `.Renviron` syntax.
4. Check your app's [development-mode access rules](https://developer.spotify.com/documentation/web-api/concepts/quota-modes).
   Development apps require the owner to have Spotify Premium, and test
   users need to be on the app's allowlist.

## Shiny app example

Save this as `app.R`, run it, and open `http://127.0.0.1:8100` in a regular
browser. After connecting Spotify, click **Load top tracks**.

```{r, eval = FALSE}
library(shiny)
library(shinyOAuth)

client <- oauth_client(
  provider = oauth_provider_spotify(),
  client_id = Sys.getenv("SPOTIFY_OAUTH_CLIENT_ID"),
  client_secret = Sys.getenv("SPOTIFY_OAUTH_CLIENT_SECRET"),
  redirect_uri = "http://127.0.0.1:8100",
  scopes = c("user-read-private", "user-top-read")
)

ui <- oauth_ui(fluidPage(
  h2("My top tracks"),
  actionButton("load", "Load top tracks"),
  tableOutput("tracks")
), id = "auth", client = client)

server <- function(input, output, session) {
  auth <- oauth_module_server("auth", client)

  tracks <- eventReactive(input[["load"]], {
    req(auth[["authenticated"]])
    tryCatch({
      response <- perform_resource_req(
        auth[["token"]],
        "https://api.spotify.com/v1/me/top/tracks",
        query = list(limit = 10, time_range = "short_term")
      )
      httr2::resp_check_status(response)
      body <- httr2::resp_body_json(response, simplifyVector = FALSE)
      vapply(body[["items"]], function(track) track[["name"]], character(1))
    }, error = function(e) NULL)
  })

  output[["tracks"]] <- renderTable({
    req(auth[["authenticated"]])
    values <- tracks()
    validate(need(!is.null(values), "Could not load tracks. Try again later."))
    validate(need(length(values) > 0, "No listening data to show yet."))
    data.frame(Track = values)
  })
}

runApp(shinyApp(ui, server), port = 8100, launch.browser = FALSE)
```

## API requests and scopes

`user-top-read` grants permission to read listening preferences. The request
above asks for up to ten tracks from the short-term range. The module obtains
and stores the access token; `perform_resource_req()` uses it to call the API.
`eventReactive()` loads the data when the button is clicked, and
`renderTable()` displays the result.

For other data, choose the API URL and requested scopes together. A successful
login does not mean every API is available to the app. Empty results can be
normal for accounts without listening history; API errors can indicate missing
permissions, app access restrictions, or request limits.

## Full dashboard example

A larger example is installed with the package. It adds profile information,
currently playing music, top artists, and recent tracks, with a bslib layout.
It uses the same authentication setup as the example above.

```{r, eval = FALSE}
install.packages(c("bslib", "ggplot2", "DT", "purrr", "dplyr"))
dashboard_file <- system.file(
  "examples", "spotify-dashboard.R", package = "shinyOAuth", mustWork = TRUE
)
file.show(dashboard_file)  # Read or copy the complete source.
source(dashboard_file)    # Run after setting your Spotify credentials.
```

The source groups the code into client setup, API helpers, UI, and server
logic. It validates Spotify links and image URLs before rendering them and
keeps table escaping enabled. Preserve those protections when adapting it;
profile and track text can contain characters that would otherwise become HTML.
Available data still depends on Spotify's API access rules for your app.
