::use_description(
usethisfields = list(
Package = 'monthAppPkg',
Title = 'An example app-pkg',
Version = '0.0.0.9000',
Description = 'A shiny application built inside an R package.',
"Authors@R" = NULL,
Author = utils::person(
given = "Jane",
family = "Doe",
role = c("aut", "cre")
),Maintainer = utils::person(
given = "Jane",
family = "Doe",
email = "Jane.Doeh@email.io"
),License = "GPL-3"
) )
1 Whole app game
This chapter is modeled on the Whole Game chapter in R Packages, 2ed.1 We’ll go through the development of the monthAppPkg
Shiny app-package (adapted from Mastering Shiny).2
1.1 A toy app-package
We will briefly discuss creating an R package with a Shiny application. Each topic will be explained in detail in the next chapters. In the end, you will have a Shiny application with all the features and functions of an R package.
1.2 Package metadata
Every R package requires a DESCRIPTION
file. You can quickly create one using usethis::use_description()
.3
The values above in the fields
list avoids the boilerplate content from use_description()
.4
The essential seven fields are shown below:5
Package: monthAppPkg
Title: An example app-pkg
Version: 0.0.0.9000
Author: Jane Doe [aut, cre]
Maintainer: Jane Doe <Jane.Doeh@email.io>
Description: A shiny application built inside an R package.
License: GPL-3
You will get specific fields automatically for function documentation and dependency management.6
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3
1.3 Data
To include the birthstones.csv
data in monthAppPkg
, we’ll create a data-raw/
folder with usethis::use_data_raw("stones")
. Next, we’ll move the birthstones.csv
file into data-raw/
, load it into the Global Environment, and an R package data object witho usethis::use_data()
:
::use_data_raw("stones") usethis
✔ Setting active project to '/path/to/monthAppPkg'
✔ Creating 'data-raw/'
✔ Adding '^data-raw$' to '.Rbuildignore'
✔ Writing 'data-raw/stones.R'
• Modify 'data-raw/stones.R'
• Finish the data preparation script in 'data-raw/stones.R' • Use `usethis::use_data()` to add prepared data to package
Move birthstones.csv
to data-raw/birthstones.csv
:
::file_move(path = "birthstones.csv", new_path = "data-raw/birthstones.csv") fs
Contents of data-raw/stones.R
:
## code to prepare `stones` dataset goes here
library(vroom)
<- vroom::vroom("data-raw/birthstones.csv")
stones ::use_data(stones, overwrite = TRUE) usethis
Rows: 12 Columns: 2
── Column specification ──────────────────────────────────────────────────
Delimiter: ","
chr (2): month, stone
ℹ Use `spec()` to retrieve the full column specification for this data. ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
::use_data(stones) usethis
✔ Adding 'R' to Depends field in DESCRIPTION
✔ Setting LazyData to 'true' in 'DESCRIPTION'
✔ Saving 'stones' to 'data/stones.rda' • Document your data (see 'https://r-pkgs.org/data.html')
Data should be documented using roxygen2
which we’ll cover in the data chapter.7
1.4 Dependencies
Every Shiny app-package depends on the shiny
package. usethis::use_package()
8 adds it under the Imports
field the DESCRIPTION
file.
::use_package("shiny") usethis
✔ Adding 'shiny' to Imports field in DESCRIPTION • Refer to functions with `shiny::fun()`
We’re advised to use explicit namespacing (i.e., pkg::fun()
), but we can avoid this by importing all Shiny’s functions into our package namespace using the @import
tag from roxygen2
.9
1.5 Write code
Create new .R
files under R/
using use_r()
:
::use_r("monthFeedback") usethis
✔ Setting active project to '/projects/apps/monthAppPkg' • Modify 'R/monthFeedback.R'
Both UI and server module functions are stored in R/monthFeedback.R
and R/birthstone.R
. Tests should also be created for each function.
1.6 Loading
Shiny app development typically involves something like the following workflow:
- Write UI/server code
- Click Run App
- Rinse, repeat
When making the switch from app development to app-package development, calling load_all()
is somewhat analogous to clicking on the Run App icon–you’ll do it often (more than any other devtools
or usethis
function).
::load_all() devtools
The output we’re looking for from load_all()
is straightforward:
ℹ Loading monthAppPkg
load_all()
is similar to calling library
, but it’s specifically designed to be used during package development. Imagine sourcing all the functions in the R/
folder, but more sophisticated.
1.7 Write tests
Create tests for the code in the R/
folder using use_test()
::use_test("monthFeedbackServer") usethis
This will add test-
files in tests/testthat/
:
tests/
├── testthat/
│ └── test-monthFeedbackServer.R
└── testthat.R
2 directories, 2 files
The first time you run use_test()
, it will detect if your package has the testthat
infrastructure (and create the necessary files if you don’t).10 use_test()
will also adds the testthat
package to the Suggests
field in the DESCRIPTION
and includes the edition (currently 3
).
✔ Adding 'testthat' to Suggests field in DESCRIPTION ✔ Adding '3' to Config/testthat/edition
Tests are covered in Mastering Shiny,11 on the Shiny website,12 and in various testing packages (like shinytest
13 and shinytest2
14).
1.8 app.R
The contents of app.R
have been changed to include a call to pkgload::load_all()
the standalone app function (monthApp()
), which is stored in the R/
folder.
::load_all(".")
pkgloadmonthApp()
pkgload
needs to be listed under Imports
in the DESCRIPTION
file (just like we did with shiny
above).
::use_package("pkgload") usethis
✔ Adding 'pkgload' to Imports field in DESCRIPTION • Refer to functions with `pkgload::fun()`
Because we’re only going to use load_all()
from pkgload
, we’ll use explicit namespacing (i.e., pkg::fun()
).15
1.8.1 use_package_doc()
The use_package_doc()
creates the R/[[name]-package].R
file, which can be used as a single location for declaring dependencies in monthAppPkg
:
::use_package_doc() usethis
✔ Setting active project to 'projects/apps/monthAppPkg'
✔ Writing 'R/monthAppPkg-package.R' • Modify 'R/monthAppPkg-package.R'
We’ll use @importFrom
to add only the load_all()
function to the NAMESPACE
.
#' @keywords internal
"_PACKAGE"
## usethis namespace: start
#' @importFrom pkgload load_all
## usethis namespace: end
NULL
1.8.2 use_build_ignore()
R packages don’t typically have an app.R
file in their root folder, so we’ll let devtools
know this file should be ignored by creating a .Rbuildignore
and include a pattern that excludes app.R
whenever the package is built.
::use_build_ignore("app.R") usethis
✔ Adding '^app\\.R$' to '.Rbuildignore'
It’s best to let use_build_ignore()
handle excluding any files or folders from your package builds because it automatically writes the correct regular expression pattern.
1.9 LICENSE
Use one of the usethis
license functions to add a LICENSE
file.
::use_mit_license() usethis
The license file should match the License
field in the DESCRIPTION
file (in this case, it’s MIT
).16
✔ Adding 'MIT + file LICENSE' to License
✔ Writing 'LICENSE'
✔ Writing 'LICENSE.md' ✔ Adding '^LICENSE\\.md$' to '.Rbuildignore'
1.10 Document
After writing roxygen2
documentation for the data, modules, and standalone app function, calling devtools::document()()
generates the .Rd
files and NAMESPACE
.17
::document() devtools
The output from document()
tells us what files have been created (and if there were any errors in them).18
ℹ Updating monthAppPkg documentation
ℹ Loading monthAppPkg
Writing NAMESPACE
Writing NAMESPACE
Writing birthstoneUI.Rd
Writing birthstoneServer.Rd
Writing monthApp.Rd
Writing monthFeedbackUI.Rd Writing monthFeedbackServer.Rd
1.10.1 Namespace
The NAMESPACE
file contains the imported and exported functions from monthAppPkg
:19
# Generated by roxygen2: do not edit by hand
export(birthstoneServer)
export(birthstoneUI)
export(monthApp)
export(monthFeedbackServer)
export(monthFeedbackUI)
import(shiny) importFrom(pkgload,load_all)
1.11 RStudio project options
If you’re developing in RStudio, we need to update our .Rproj
file to enable the Build pane and keyboard shortcuts:
file.edit("monthAppPkg.Rproj")
If your app-package was initially built as an RStudio project (i.e., not as a package), the following fields should be included at the bottom of monthAppPkg.Rproj
:
BuildType: Package
PackageUseDevtools: Yes
PackageInstallArgs: --no-multiarch --with-keep.source PackageRoxygenize: rd,collate,namespace
These options are also available under Tools > Project Options … > Build Tools
1.12 Git
The use_git()
step is performed much earlier in R Packages, 2ed, but I’ve saved it for this step because using Git will prompt the IDE to re-initialize and display the Git pane (and it will also read our new settings in the .Rproj
file).
✔ Setting active project to '/projects/apps/monthAppPkg'
✔ Initialising Git repo
✔ Adding '.Rproj.user', '.Rhistory', '.Rdata', '.httr-oauth', '.DS_Store', '.quarto' to '.gitignore'
There are 12 uncommitted files:
* '.gitignore'
* '.Rbuildignore'
* 'app.R'
* 'data/'
* 'DESCRIPTION'
* 'LICENSE'
* 'LICENSE.md'
* 'man/'
* 'monthAppPkg.Rproj'
* 'NAMESPACE'
* ...
Is it ok to commit them?
1: Absolutely not
2: Not now 3: Absolutely
Agree to commit these files:
Selection: 3
✔ Adding files
✔ Making a commit with message 'Initial commit'
• A restart of RStudio is required to activate the Git pane
Restart now?
1: Not now
2: Negative 3: Absolutely
Restarting RStudio will activate the Git and Build panes:
This will also activate the devtools
keyboard shortcuts:
1.12.1 Keyboard shortcuts
The keyboard shortcuts are available in RStudio and Positron.
load_all()
Shift + Ctrl/Cmd + L
document()
Shift + Ctrl/Cmd + D
install()
Shift + Ctrl/Cmd + B
test()
Shift + Ctrl/Cmd + T
1.13 Install
Installing monthAppPkg
with devtools::install()
produces see the following output in the Build pane:
==> devtools::document(roclets = c('rd', 'collate', 'namespace'))
ℹ Updating monthAppPkg documentation
ℹ Loading monthAppPkg
Documentation completed
==> R CMD INSTALL --preclean --no-multiarch --with-keep.source monthAppPkg
* installing to library ‘/path/to/Library/R/x86_64/4.2/library’
* installing *source* package ‘monthAppPkg’ ...
** using staged installation
** R
** data
*** moving datasets to lazyload DB
** byte-compile and prepare package for lazy loading
** help
*** installing help indices
** building package indices
** testing if installed package can be loaded from temporary location
** testing if installed package can be loaded from final location
** testing if installed package keeps a record of temporary installation path * DONE (monthAppPkg)
Back in the Console, RStudio will restart and call library(monthAppPkg)
:
Restarting R session...
> library(monthAppPkg)
We can now launch the app using monthApp()
monthApp()
Launch app with the shinypak
package:
launch('01_whole-app-game')
1.14 Additional files
The following sections cover additional files you should include in your ap-package (but are not required).
1.14.1 README
A README.md
file is the initial point of contact for users and/or contributors looking for information about your app-package. use_readme_rmd()
will create a README.Rmd
(i.e., the file you’ll edit), which serves as the source document for your README.md
.
::use_readme_rmd() usethis
The README.Rmd
pattern is automatically added to the .Rbuildignore
, and includes a Git ‘pre-commit’ hook:20
✔ Adding '^README\\.Rmd$' to '.Rbuildignore' ✔ Writing '.git/hooks/pre-commit'
1.14.2 NEWS.md
A NEWS.md
is helpful for logging updates to your app-package and tracking release information.
::use_news_md() usethis
use_news_md()
will also prompt me to add and commit this file to the Git repository:
There is 1 uncommitted file:
* 'NEWS.md'
Is it ok to commit it?
1: Negative
2: Yeah
3: Absolutely not
Selection: 2
✔ Adding files ✔ Making a commit with message 'Add NEWS.md'
The contents of the NEWS.md
are below:21
# monthAppPkg (development version)
* Initial CRAN submission.
1.14.3 Vignettes
Vignettes can be used to store detailed tutorials, explanations of core concepts, use-cases, FAQs and troubleshooting, integration with other packages, etc.
use_vignette("monthAppPkg")
The first time we call use_vignette()
will prompt usethis to add the following fields in the DESCRIPTION
:
✔ Adding 'knitr' to Suggests field in DESCRIPTION
✔ Adding 'rmarkdown' to Suggests field in DESCRIPTION ✔ Adding 'knitr' to VignetteBuilder
The following files are also included in the .gitignore
:
✔ Adding 'inst/doc' to '.gitignore'
✔ Creating 'vignettes/' ✔ Adding '*.html', '*.R' to 'vignettes/.gitignore'
1.15 Recap
In this chapter we’ve covered the steps used to create a package containing a Shiny application.
The example app comes from the Packages chapter of Mastering Shiny↩︎
I’ve stored the code for this application in the
01_whole-app-game
branch of thesap
repository (to avoid confusing it with the actual application repo for this chapter).↩︎The Whole Game chapter of R Packages, 2ed begins with the
usethis::create_package()
function, which callsusethis::use_description()
internally.↩︎At the time this was written, there are over 4,000 hits with the boilerplate value for
Description
(i.e.,"What the package does"
), which is a sign of how muchusethis
has been adopted (and how often people forget to come back and edit theirDESCRIPTION
file).↩︎If you frequently develop R packages or Shiny apps, consider adding these fields to your
.Rprofile
.↩︎Always leave an empty final line in the
DESCRIPTION
.↩︎View the documented
stones
dataset here on GitHub.↩︎Whenever you use a function from another package, start by running
usethis::use_package()
to ensure it’s in theDESCRIPTION
file.↩︎I’ve included
@import shiny
above the definition of our standalone app function (R/launch_app.R
), which means I don’t need to addshiny::
when using Shiny functions belowR/
.↩︎You can also set up the
testthat
infrastructure by callingusethis::use_testthat()
↩︎The Testing chapter in Mastering Shiny covers unit tests with
testthat
,shiny::testServer()
, and theshinytest
package.↩︎See the ‘Server Function Testing’ article on the Shiny website for more information on
testServer()
↩︎Check the
shinytest
package website and video tutorial for more information on testing your app.↩︎shinytest2
is an updated verison ofshinytest
with excellent documentation and videos.↩︎We typically call
devtools::load_all()
, but usingpkgload
reduces the number of dependencies included withdevtools
. Read more aboutpkgload
in the ‘Conscious uncoupling’ ofdevtools
.↩︎use_mit_license()
will automatically include theLICENSE.md
file in the root folder (and includes the necessary pattern in the.Rbuildignore
to exclude it from the package builds).↩︎You can include an Roxygen skeleton in the IDE by clicking on Code > Insert Roxygen Skeleton, or using the keyboard shortcut: Option/⌥ + Shift⇧ + Ctrl/Cmd + R↩︎
The files created by
document()
rely on theroxygen2
package (and should not be edited manually).↩︎We’re importing the everything from
shiny
and onlyload_all
frompkgload
):↩︎This Git behavior is designed to prevent us from making changes to the
README.Rmd
and forgetting to re-render theREADME.md
. If you find this behavior confusing or would like to disable it, run the following commands in the Terminal:rm .git/hooks/pre-commit
↩︎The
Initial CRAN submission
bullet doesn’t apply tomonthAppPkg
, so I’ll remove it and re-commit/push theNEWS.md
file.↩︎