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.