25  rhino


WARNING: rhino isn’t like the previous two frameworks we’ve covered in this section, because rhino doesn’t create an app-package:

  • rhino apps rely on renv and box for managing imported dependencies (instead of the DESCRIPTION and NAMESPACE files in an R package).

  • rhino requires node.js, an open-source JavaScript runtime environment.


This chapter briefly describes a version of moviesApp built using rhino. The resulting app (rap) is in the 24_rhino branch.

The branch in this chapter is slightly different than the previous golem and leprechaun branches, because instead of loading, documenting, and installing rap, we’re going to re-initialize the IDE by selecting Session > Terminate R…

(a) Re-initialize the IDE
Figure 25.1: On the 24_rhino branch, re-initialize the IDE (instead of loading, documenting, and installing)

When the IDE re-opens, we see the rap files and notice the Build pane has been removed:

(a) rhino app IDE
Figure 25.2: Notice the Build pane has been removed from the 24_rhino branch

The Build pane is deactivated because rhino applications aren’t R packages.1

Launch the application in rap by opening the app.R file and clicking Run App (or by passing rhino::app() into the Console).

Launch app with the shinypak package:

launch('24_rhino')
(a) Calling rhino::app()
Figure 25.3: Running the application in rap

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:

library(shinypak)
list_apps(regex = 'rhino')
## # A tibble: 1 × 2
##   branch   last_updated       
##   <chr>    <dttm>             
## 1 24_rhino 2024-04-11 12:10:16

Launch the app:

launch(app = "24_rhino")

Download the app:

get_app(app = "24_rhino")

25.1 rap (a rhino app)

The files in rap are below:

├── .Rprofile
├── .github/
   └── workflows
├── .gitignore
├── .lintr
├── .renvignore
├── .rscignore        
├── README.md
├── app
   ├── js
   ├── logic
   ├── main.R
   ├── static
   ├── styles
   └── view
├── app.R
├── config.yml
├── dependencies.R.
├── moviesApp.Rproj
├── renv
   ├── .gitignore.   
   ├── activate.R
   ├── library
   ├── settings.json
   └── staging
├── renv.lock
├── rhino.yml
└── tests
    ├── cypress
    ├── cypress.json
    └── testthat

24 directories, 31 files
1
Activates the renv package
2
CI/CD via GitHub actions
3
Lintr (from lintr package)
4
renv ignore (works like .gitignore)
5
rhino app dependencies
6
renv library of packages in app project

As we can see, most of the standard R package folders and files are missing from rap, because rhino applications use the box package for importing dependencies and organizing code.2

25.2 rhino features

The rhino website explains the philosophy behind the application structure above, so I won’t repeat that information here. However, I highly recommend reading the available documentation on rhino and box before deciding to adopt this framework.3

25.3 box modules

A box module (not to be confused with a Shiny module) is a collection of .R scripts in a specified folder. The modules in a new rhino app are stored in the app/logic/ and app/view/ folders:4

app
├── js/
├── logic/
├── main.R
├── static/
├── styles/
└── view/

6 directories, 1 file
1
JavaScript code
2
Non-shiny code
3
Primary app file
4
Static .js or .css
5
App CSS files
6
Shiny modules and app code

25.3.1 Utility functions

In rap, I’ve placed the non-Shiny utility functions (i.e., the business logic) in app/logic:

app/logic
├── __init__.R
├── data.R
└── plot.R

1 directory, 4 files
1
Load movies data
2
scatter_plot() utility function

25.3.2 Shiny modules

Our Shiny box modules are placed in app/view, and separated into inputs and display:

app/view
├── __init__.R
├── display.R
└── inputs.R

1 directory, 3 files
1
similar to the code from R/mod_var_input.R
2
similar to the code from R/mod_scatter_display.R

app/view/inputs collects and returns the reactive values from the UI. The app/view/display module includes the app/logic/data and app/logic/plot modules.

# app/view/display.R

# import data and plot modules
box::use(
  app / logic / data,
  app / logic / plot
)

25.4 app/main.R

The app/main.R file contains the primary UI and Server functions for the application. This file adds the shiny functions and the inputs and display modules from app/view:

# app/main.R

# shiny functions
box::use(
  shiny[
    NS, fluidPage, sidebarLayout, sidebarPanel,
    mainPanel, fluidRow, column, tags, icon,
    textOutput, moduleServer, renderText
  ]
)

# import modules
box::use(
  # load inputs module ----
  app / view / inputs,
  # load display module ----
  app / view / display
)

Note that we don’t need to import app/logic modules in app/main.R, because they’re imported in their respective app/view modules.

25.5 Tests

rhino apps have support for testing with testthat, shiny::testServer(), shinytest2, and Cypress.

tests/
├── cypress
   └── integration
       └── app.spec.js
├── cypress.json
└── testthat
    └── test-main.R

4 directories, 3 files
1
Cypress test infrastructure
2
testthat test infrastructure

Below is the boilerplate test code in the tests/testthat/test-main.R file:

box::use(
  shiny[testServer], 
  testthat[...],
)

box::use(
  app/main[...],
)

test_that("main server works", {
  testServer(server, {
    expect_equal(output$message, "Hello!")
  })
})
1
box module importing test package functions
2
Using shiny::testServer() and testthat::test_that() functions in test.

I’ve included tests for the utility functions and modules in the 24_rhino branch, but I’ll cover testing with rhino elsewhere.5

25.6 rhino dependencies

In rhino apps, dependencies are managed by renv and the dependencies.R file. The renv package is designed to,

“create[s] and manage[s] project-local R libraries, save[s] the state of these libraries to a ‘lockfile’, and later restore[s] the library as required.” 6

The rhino::pkg_install() helper function updates both the dependencies.R file and renv library. Using dependencies.R, renv, and box modules removes the need to manage dependencies in a DESCRIPTION or NAMESPACE file.7

Recap

RECAP  


rhino takes a novel and innovative approach to developing Shiny applications (and covering all the ways they differ from app-packages is beyond the scope of this book). Feel free to review the code in the 24_rhino branch for a better understanding of how the box modules are structured and used within the ui and server.

The rhino framework isn’t used as wildly golem,8 but it’s been gaining popularity (and has been used in a recent pilot FDA submission).

rhino CRAN downloads

Please open an issue on GitHub


  1. I re-initialize the session on the 24_rhino branch so I’m not tempted to load, document, install, or test the code using the IDE.↩︎

  2. Imported dependencies in rhino apps use box modules instead of the DESCRIPTION and NAMESPACE.↩︎

  3. Be sure to read up on testing box modules and rhino applications with cypress and shinytest2.↩︎

  4. rhino recommends placing non-Shiny code in the app/logic folder and keeping all Shiny modules and reactive code in app/view.↩︎

  5. See the Shiny frameworks supplemental website for more information on testing your rhino app.↩︎

  6. As described in renv’s DESCRIPTION file↩︎

  7. Be sure to read the renv configuration article for a better understanding on how it works with rhino apps.↩︎

  8. Check for yourself on cran-downloads↩︎