Chapter 8 Additional Coding Practices
The following coding practices arise much less frequently than our Specific Coding Practices and HTML/CSS. However, being aware of them will help you out in the long run.
8.1 Plot Caching
Of these less frequent practices, Plot Caching is perhaps the most useful. Plot caching refers to the practice of your app to generate a plot once, save it, and then quickly serve the plot again and again to users. In essence, this is a method for improving the efficiency of an app. However, this is not something that always needs to be used.
The following cases represent some of the most common places where using plot caching is beneficial:
- If your app includes a computationally intense plot (i.e., lots of data and/or layers) whose underlying data is fixed.
- If your app has a dynamic graph that the user explores and needs to move back and forth between various states.
These cases represent a place where the performance of your app can suffer. To improve performance, you should consider using the renderCachedPlot
function rather than renderPlot
. This function will store a copy of each plot on the sever and provide that stored copy to new instances of the app. This cuts down on server demands and speeds up your app. For the second category, this allows the users to move more quickly to a previously examined state (i.e., low to no hang time).
There are two key arguments to renderCachedPlot
: expr
and cacheKeyExpr
. The expr
is the code chunk which will generate the plot or the name of plot object you’ve created elsewhere.
The cacheKeyExpr
is the listing of all reactive
elements that you want to tie the plot to. For example, if we set cacheKeyExpr = { list(input$sampleSize, input$selectedData) }
, then the app will tie each saved version of the plot to the values of sampleSize
and selectedData
. The user sets these two inputs to a new pairing, your app would re-generate the plot. However, when the user sets them to a pairing that they’ve already looked at, your app immediately re-displays the saved plot. Plot caching also looks across all of the users, thus speeding up your app’s plotting for all users.
8.2 Minimize External Files
Try to minimize the number and size of external files you’re attaching to your App. If you’re working on an existing app, remove any external files that are no longer necessary.
Wherever possible try to place external files into the www
directory/folder that is at the same level as your app.R
file.
If there are any external files (e.g., *.csv
, *.txt
, *.dat
, *.jpg
) that are not being used, delete them from the repository.
8.3 Metadata
As of boastUtils v0.1.10
, your App will need the following metadata in a file named DESCRIPTION (no file extension). This data is used to inform Learning Record Stores (LRS) about your App as well as provide information to instructors using the Instructor App (unreleased). See also our section on the DESCRIPTION file in our chapter on the App Template.
DESCRIPTION
Title: Sample App - A Lengthy Title
ShortName: Sample App
Date: 2021-05-13
Lifecycle: experimental
Authors@R: c(
person(given = "Neil", family = "Hatfield", email = "neil.hatfield@psu.edu", role = c("aut", "cre")),
person(given = "Robert", family = "Carey", email = "rpc5102@psu.edu", role = c("aut"))
)
Chapter: Sample Chapter
Description: This app is focused on the common types of xyz.
LearningObjectives: c(
"The student will learn to understand Concept A in way z.",
"The student will learn to understand Concept B as description y."
)
DisplayMode: Normal
URL: https://psu-eberly.shinyapps.io/Sample_App
BugReports: https://github.com/EducationShinyAppTeam/Sample_App/issues
License: CC-BY-NC-SA-4.0
Tags: simulation
Type: Shiny
8.4 Other Languages (Python, JavaScript, etc.)
Generally speaking, we want to keep the vast majority of our work within the R language (with the necessary HTML and CSS spots). Due to our turnover of coders, we won’t always be able to ensure that we get someone who is proficient in additional languages such as Python, Rust, Netlogo, JavaScript, etc.
See if what you’re trying to do can first be done in R. If not, set up a time to speak with Neil to see about the appropriate way to go about using another language.
8.5 Using rLocker
Because these apps are being developed for educational purposes, it is beneficial to collect data (logfiles) based on learning objectives and outcomes. The rLocker package was created to aid in this process. It is important to know that the package itself does not perform feats of magic and will require some tuning to get right.
If you are following the directions we’ve laid out in this Style Guide, there is no additional action that you have to take. We provide the following information just for those individuals who would like to learn more about rLocker
.
8.5.2 Setup
Core configuration is included in the boastUtils package. Simply use the boastApp wrapper function instead of shinyApp and you’re done! See Creating an App for more information on how to use boastApp.
8.5.3 Usage
The main purpose of this package is to help create meaningful- structured- xAPI data. xAPI (Experience API) can be thought of as the “Who did what, where did they do it, and when did they do it?” in your App. Statements are often structured as Actor, Verb, and Object that can also Result in something. For more on xAPI, check out What is the Experience API? or experiment with Statements in the xAPI Lab.
For example:
Bob (Actor) clicked (Verb) submit (Object).
In assessments:
Neil (Actor) answered (Verb) Question 1 (Object) correctly with the answer true (Result).
This is a good way to think about it when beginning to write collection functions. Which brings us to our next part, writing collection functions.
Out of the box, rocker
does not provide any collection functions for apps, only creation and storage mechanisms. Why is this? Every app is different! You know your app the best and what interactions are possible. Before storing data in a Learning Record Store (LRS) like Learning Locker, it is important to transform it in a way that makes sense.
boastUtils
If using the boastApp
wrapper for your project, your App will automatically generate and store a few of the more typical Statements. Please refrain from creating additional versions of the following statement descriptions:
Statement | Description |
---|---|
launched | User has started the app. |
experienced | User has visited a tab (page) within the app. |
exited * | User has left the app. |
An App should only have one launched
and exited
event but can have multiple experiences per session. Therefore, you may use experienced
in other places within your App.
* Requires boastUtils >= 0.1.5
.
Sample generator function
.generateStatement <- function(
session,
verb = NA,
object = NA,
description = NA,
interactionType = NA,
response = NA,
success = NA,
completion = FALSE)
{
statement <- rLocker::createStatement(list(
verb = verb,
object = list(
id = paste0(getCurrentAddress(session), "#", object),
name = paste0(APP_TITLE),
description = paste0("Question ", activeQuestion, ": ", description),
interactionType = interactionType
),
result = list(
success = success,
response = response,
completion = completion
)
))
return(rLocker::store(session, statement))
}
Sample event observer
observeEvent(session$input[[id]], {
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# BEGIN VALIDATION #
# #
# For this example, #
# We will check if someone answered a question in our game correctly. #
# Your code should be different here! #
# #
# The input element we're observing. #
object <- id
# The user's action verb. Most likely going to be answered in this case. #
# Run rLocker::getVerbList() if you are unsure of the options. #
verb <- "answered"
# The correct answer to the question. #
answer <- gameSet[id, "answer"]
# The user's answer to the question. #
response <- input$ans
# A description of the question or the full question itself (if short). #
description <- gameSet[id,]$question
# The type of question it is. #
# Run rLocker::getInteractionTypes() if you are unsure of the options. #
interactionType <- ifelse(
gameSet[id,]$format == "numeric", "numeric", "choice"
)
# Was the question answered successfully? #
success <- input$ans == answer
# Did this event trigger the completion of your activity? #
completion <- ifelse(.appState == "continue", FALSE, TRUE)
# #
# END VALIDATION #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
.generateStatement(
session,
object = object,
verb = verb,
description = description,
response = response,
interactionType = interactionType,
success = success,
completion = completion
)
}
Additional code can be found in the examples folder as well as the main README of the rLocker project; apps such as the Hypothesis_Testing_Game have already been outfitted with rLocker. The statement generator above will eventually make its way into boastUtils
once enough feedback is collected. If you are ever confused about how it works, feel free to reach out to Bob (rpc5102).
Did you know?
You can run help(package = "rLocker")
to view the help files for this package or press F1
while typing a function name to see the documentation for that specific function.