1  Whole app game

I’ve created the shinypak R package In an effort to make each section accessible and easy to follow:

Install shinypak using pak (or remotes):

# install.packages('pak')
pak::pak('mjfrigaard/shinypak')

Review the chapters in each section with list_apps()

library(shinypak)
list_apps(regex = '^01')
## # A tibble: 1 × 2
##   branch            last_updated       
##   <chr>             <dttm>             
## 1 01_whole-app-game 2024-01-17 12:30:27

Launch the app:

launch(app = "01_whole-app-game")

Download the app:

get_app(app = "01_whole-app-game")

This chapter is modeled on the Whole Game chapter in R Packages, 2ed. We’ll go through the development of a small shiny app-package.1 The monthAppPkg example we’re going to be developing has been adapted from Mastering Shiny.2

1.1 A toy app-package

Rather than duplicating how the devtools/usethis functions are used while writing R packages, I’m going to focus on the areas that differ from Shiny application development (each section is covered in more detail in the following chapters). The final result will be a Shiny application with all the devtools features and functionality.

1.2 use_description()

Every package needs a DESCRIPTION–calling devtools::load_all() without a DESCRIPTION will result in an error. The quickest way to create a description file is with usethis::use_description().3

usethis::use_description(
  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"))

I’ve provided values to the fields list so this file isn’t generated with the boilerplate contents. The documentation for use_description() mentions putting this in your .Rprofile if you develop a lot of R packages (or app-packages!).4

Every R package needs the following seven fields: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

We get the following fields for free (they will be required for function documentation and dependency management).

Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3

1.3 use_data()

To include the stones data in monthAppPkg, we create a script in data-raw/ with usethis::use_data_raw("stones"), then move the birthstones.csv file into data-raw/, load it into the Global Environment and pass it to usethis::use_data():

usethis::use_data_raw("stones")
✔ 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:

fs::file_move(path = "birthstones.csv", new_path = "data-raw/birthstones.csv")

Contents of data-raw/stones.R:

## code to prepare `stones` dataset goes here
library(vroom)
stones <- vroom::vroom("data-raw/birthstones.csv")
usethis::use_data(stones, overwrite = TRUE)
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.
usethis::use_data(stones)
✔ 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,6 which we’ll cover in the data chapter.

1.4 use_package()

Every Shiny app-package will need to import the shiny package as a dependency, which starts by listing it under the Imports field the DESCRIPTION file. We can do this using usethis::use_package().7

usethis::use_package("shiny")
✔ 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 Shiny’s functions into our package namespace.

The @import tag from roxygen2 can be used to import “the entire [shiny] namespace” into monthAppPkg.8

1.5 load_all()

Shiny app development typically involves something like the following workflow:

  1. Write UI code
  2. Write server code
  3. Click Run App
  4. Rinse, repeat

When making the switch 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).

devtools::load_all()

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.

1.6 use_r()

Create new .R files under R/ using use_r():

usethis::use_r("monthFeedback")
✔ 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.7 use_test()

Create new test- files under tests/testthat/ using using use_test():

usethis::use_test("monthFeedbackServer")

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).9

Calling use_test() 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

The tests/ folder will hold the necessary testing folder and files:

tests/
├── testthat/
   ├── test-birthstoneServer.R
   └── test-monthFeedbackServer.R
└── testthat.R

2 directories, 3 files

Tests are covered in Mastering Shiny,10 on the Shiny website,11 and in various testing packages (like shinytest12 and shinytest213)

1.8 app.R contents

The contents of app.R have been converted to a standalone app function (monthApp()), which is stored in the R/ folder.

The new contents of app.R includes a call to pkgload::load_all() and monthApp():

pkgload::load_all(".")
monthApp()

pkgload needs to be listed under Imports in the DESCRIPTION file (just like we did with shiny above).

usethis::use_package("pkgload")
✔ 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()).14

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:

usethis::use_package_doc()
✔ 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.

usethis::use_build_ignore("app.R")
✔ 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

Including a LICENSE file can be done with one of the usethis license functions. The license file should match the License field in the DESCRIPTION file (in this case, it’s MIT).

usethis::use_mit_license()

use_mit_license() will automatically include the LICENSE.md file in the root folder (and includes the necessary pattern in the .Rbuildignore to exclude it from the package builds).

✔ Adding 'MIT + file LICENSE' to License
✔ Writing 'LICENSE'
✔ Writing 'LICENSE.md'
✔ Adding '^LICENSE\\.md$' to '.Rbuildignore'

1.10 document()

All of the files below R/ should include roxygen2 documentation. 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

After writing the documentation for the data, modules, and standalone app function, calling devtools::document()() generates the .Rd files and NAMESPACE

devtools::document()

The output from document() tells us what files have been created (and if there were any errors in them).15

ℹ 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 exported functions from monthAppPkg, and the two imports (shiny and pkgload::load_all()):

# 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 Project options

In order to enable the Build pane and keyboard shortcuts in the IDE, we need to update our .Rproj file. We can edit this file using file.edit():

file.edit("monthAppPkg.Rproj")

If monthAppPkg 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 use_git()

use_git() 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

We’ll agree to commit these files to Git:

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:

Git Pane

Build Pane

This will also activate the devtools keyboard shortcuts:

1.12.1 devtools keyboard shortcuts

1.12.1.1 load_all()

Shift + Ctrl/Cmd + L

1.12.1.2 document()

Shift + Ctrl/Cmd + D

1.12.1.3 install()

Shift + Ctrl/Cmd + B

1.12.1.4 test()

Shift + Ctrl/Cmd + T

1.13 install()

Now we’re ready to install monthAppPkg with devtools::install(), which 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()

Our monthApp() application

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 use_readme_rmd()

The README.md file is usually the initial point of contact for users and 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.

usethis::use_readme_rmd()

The README.Rmd pattern is automatically added to the .Rbuildignore, and includes a Git ‘pre-commit’ hook:

✔ Adding '^README\\.Rmd$' to '.Rbuildignore'
✔ Writing '.git/hooks/pre-commit'

This Git behavior is designed to prevent us from making changes to the README.Rmd and forgetting to re-render the README.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

1.14.2 use_news_md()

A NEWS.md is helpful for logging updates to your app-package and tracking release information.

usethis::use_news_md()

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:

# monthAppPkg (development version)

* Initial CRAN submission.

The 2nd bullet doesn’t apply to monthAppPkg, so I’ll remove it and re-commit/push the NEWS.md file.

1.14.3 use_vignette()

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'

Please open an issue on GitHub


  1. The example app comes from the Packages chapter of Mastering Shiny↩︎

  2. I’ve stored the code for this application in the 01_whole-app-game branch of the moviesApp repository (to avoid confusing it with the actual application repo for this chapter).↩︎

  3. The Whole Game chapter of R Packages, 2ed begins with the usethis::create_package() function, which calls usethis::use_description() internally.↩︎

  4. 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 much usethis has been adopted (and how often people forget to come back and edit their DESCRIPTION file).↩︎

  5. Always leave an empty final line in the DESCRIPTION↩︎

  6. View the documented stones dataset here on GitHub.↩︎

  7. Whenever you use a function from another package, start by running usethis::use_package() to ensure it’s in the DESCRIPTION file.↩︎

  8. I’ve included @import shiny above the definition of our standalone app function (R/launch_app.R), which means I don’t need to add shiny:: when using Shiny functions belowR/.↩︎

  9. You can also set up the testthat infrastructure by calling usethis::use_testthat()↩︎

  10. The Testing chapter in Mastering Shiny covers unit tests with testthat, shiny::testServer(), and the shinytest package.↩︎

  11. See the ‘Server Function Testing’ article on the Shiny website for more information on testServer()↩︎

  12. Check the shinytest package website and video tutorial for more information on testing your app.↩︎

  13. shinytest2 is an updated verison of shinytest with excellent documentation and videos.↩︎

  14. We typically call devtools::load_all(), but using pkgload reduces the number of dependencies included with devtools. Read more about pkgload in the ‘Conscious uncoupling’ of devtools.↩︎

  15. The files created by document() rely on the roxygen2 package (and should not be edited manually).↩︎