Skip to contents

Scope

This guide shows the recommended explicit learnr workflow for learnrTrackR. The package does not intercept browser events or internal learnr progress state. Instead, it creates a small tracking context in the tutorial setup chunk and reuses that context in tracked questions and gradethis checks.

The workflow is designed for local teaching prototypes and early course experiments where the learner identifier is supplied by the tutorial launch environment.

Read the launch environment

In a tutorial, the setup chunk should first validate the launch environment. For a local prototype, default_db_path and default_group_id can be used as fallbacks.

library(learnrTrackR)

db_path <- tempfile(fileext = ".sqlite")

withr::local_envvar(LEARNRTRACKR_STUDENT_ID = "student_001")

tracking_env <- get_learnr_tracking_env(
  default_db_path = db_path,
  default_group_id = "A"
)

tracking_env
#> $student_id
#> [1] "student_001"
#> 
#> $db_path
#> [1] "/tmp/RtmpeRN0pd/file1f3379fb4f59.sqlite"
#> 
#> $group_id
#> [1] "A"
#> 
#> $student_envvar
#> [1] "LEARNRTRACKR_STUDENT_ID"
#> 
#> $db_envvar
#> [1] "LEARNRTRACKR_DB"
#> 
#> $group_envvar
#> [1] "LEARNRTRACKR_GROUP_ID"
#> 
#> attr(,"class")
#> [1] "learnrTrackR_env"

Create the context

The setup chunk should create one context and keep it available to later question and check chunks.

tracking <- setup_learnr_tracking(
  tutorial_id = "module_01",
  student_id = tracking_env$student_id,
  db_path = tracking_env$db_path,
  group_id = tracking_env$group_id
)

tracking
#> $student_id
#> [1] "student_001"
#> 
#> $tutorial_id
#> [1] "module_01"
#> 
#> $db_path
#> [1] "/tmp/RtmpeRN0pd/file1f3379fb4f59.sqlite"
#> 
#> $group_id
#> [1] "A"
#> 
#> $config_path
#> [1] NA
#> 
#> attr(,"class")
#> [1] "learnrTrackR_context"

setup_learnr_tracking() initializes the SQLite database and registers the current learner. Use open_learnr_tracking_db() when a chunk needs a database connection.

con <- open_learnr_tracking_db(tracking)

register_questions(
  con,
  tutorial_id = tracking$tutorial_id,
  questions = data.frame(
    question_id = c("q_radio", "q_code"),
    question_label = c("Mean multiple choice", "Mean code exercise"),
    question_type = c("radio", "code"),
    max_score = c(1, 1)
  )
)
#> # A tibble: 2 × 6
#>   question_id tutorial_id question_label      question_type max_score created_at
#>   <chr>       <chr>       <chr>               <chr>             <dbl> <chr>     
#> 1 q_code      module_01   Mean code exercise  code                  1 2026-06-2…
#> 2 q_radio     module_01   Mean multiple choi… radio                 1 2026-06-2…

get_students(con)
#> # A tibble: 1 × 5
#>   student_id  student_label email group_id created_at          
#>   <chr>       <chr>         <chr> <chr>    <chr>               
#> 1 student_001 student_001   NA    A        2026-06-24T12:25:35Z
get_questions(con, tutorial_id = tracking$tutorial_id)
#> # A tibble: 2 × 6
#>   question_id tutorial_id question_label      question_type max_score created_at
#>   <chr>       <chr>       <chr>               <chr>             <dbl> <chr>     
#> 1 q_code      module_01   Mean code exercise  code                  1 2026-06-2…
#> 2 q_radio     module_01   Mean multiple choi… radio                 1 2026-06-2…

DBI::dbDisconnect(con)

Track a learnr question

For built-in learnr questions, pass the context to tracked_question(). This chunk is not evaluated in the vignette because learnr question constructors must run inside a learnr tutorial document.

question <- tracked_question(
  "What is the mean of 2, 4, and 6?",
  learnr::answer("3"),
  learnr::answer("4", correct = TRUE, message = "Correct."),
  learnr::answer("6"),
  type = "radio",
  question_id = "q_radio",
  context = tracking,
  max_score = 1
)

learnr::question_is_correct(question, "4")

When learnr evaluates the answer in a running tutorial, learnrTrackR records one attempt.

Track a gradethis check

For code exercises, use track_gradethis_attempt() inside gradethis::grade_this(). The key point is that the same tracking context can replace repeated con, student_id, and tutorial_id arguments.

gradethis::grade_this({
  correct <- isTRUE(all.equal(.result, mean(c(2, 4, 6))))
  feedback <- if (correct) {
    "Correct."
  } else {
    "Use mean(c(2, 4, 6))."
  }

  learnrTrackR::track_gradethis_attempt(
    context = tracking,
    question_id = "q_code",
    submitted_answer = .user_code,
    correct = correct,
    feedback = feedback,
    max_score = 1
  )
})

The same helper can be called directly in ordinary R code for testing.

track_gradethis_attempt(
  context = tracking,
  question_id = "q_code",
  submitted_answer = "mean(c(2, 4, 6))",
  correct = TRUE,
  feedback = "Correct.",
  max_score = 1
)
#> <gradethis_graded: [Correct] Correct.>

Inspect the gradebook

con <- open_learnr_tracking_db(tracking)

get_attempts(con)
#> # A tibble: 1 × 12
#>   attempt_id session_id        student_id tutorial_id question_id attempt_number
#>        <int> <chr>             <chr>      <chr>       <chr>                <int>
#> 1          1 session_student_… student_0… module_01   q_code                   1
#> # ℹ 6 more variables: submitted_answer <chr>, grade_status <chr>, score <dbl>,
#> #   max_score <dbl>, feedback <chr>, timestamp <chr>
gradebook(con, tutorial_id = tracking$tutorial_id, rule = "last")
#> # A tibble: 1 × 9
#>   student_id  tutorial_id score max_score percent n_questions n_answered
#>   <chr>       <chr>       <dbl>     <dbl>   <dbl>       <int>      <int>
#> 1 student_001 module_01       1         2      50           2          1
#> # ℹ 2 more variables: n_unanswered <int>, completed <lgl>

DBI::dbDisconnect(con)

Minimal setup chunk pattern

A real tutorial setup chunk usually reads student_id, group_id, and db_path from environment variables:

tutorial_id <- "module_01"
tracking_env <- learnrTrackR::get_learnr_tracking_env()

tracking <- learnrTrackR::setup_learnr_tracking(
  tutorial_id = tutorial_id,
  student_id = tracking_env$student_id,
  db_path = tracking_env$db_path,
  group_id = tracking_env$group_id,
  config_path = "config"
)

The returned tracking object should then be passed to tracked questions and code checks throughout the tutorial.