saving sessions (+ shinylive)

It pains me 😩 that whenever I contemplate using shiny for a work-related project, the question of allowing users to save and restore their progress πŸ’¬πŸ—¨ inevitably arises.

While there are a variety of ways to do so in shiny (e.g. bookmarks), work-related apps aren’t always so straight forward πŸ”€

For example:

  1. They are usually tied to remote raw data that must be accessed with a live connection πŸ“²
  2. They involve intermediate calculations that aren’t easily restored by simply saving and reloading input values on the UI πŸ‘¨β€πŸ”¬

Well, I finally took the time to πŸ”¨ and I’m here to share what I’ve learned πŸ‘¨β€πŸ«


⚑️Sidebar⚑️ Today is a bit of a 2 for 1. The demo app I’ve cooked up is using shinylive! In a nutshell, the app is ran serverless in the browser via webR. More on that later.



Approach

For simplicity, I’ve crafted a small app enabling users to upload a SAS Transport (.XPT) data file. The file is parsed using the {haven} package and presented as a table on the UI. There’s also an additional textInput field for users to enter their name.

Upon visiting the app, users can either:

  1. Start by uploading a fresh file πŸ“„
  2. Begin by restoring a previously saved session πŸ’Ύ

This is achieved through a radioButtons control. Upon making a choice, the corresponding fileInput widget is displayed.

When a fresh file is uploaded πŸ“€, it is read in as binary πŸ‘¨β€πŸ’» (using readBin and writeBin) in a reactiveValues container. Additional πŸ›  steps, such as implementing haven::read_xpt() to another value in the reactiveValues, occur to make it immediately available for use in the app and later. This process unfolds inside an observeEvent πŸ”.

The “session saving” ✨ is managed by a downloadHandler πŸ“₯. The content is populated by all current values in the reactiveValues via reactiveValuesToList πŸ’ͺ. This includes the binary representation of the .XPT file and possibly the processed version. Additionally, other inputs 🎚 can be arbitrarily specified for storage in the file (remember the random textInput asking for your name?).

So, what happens when you start the app by wanting to restore a previous session❓️ Using the radioButton choice for that, the appropriate fileInput widget is first made available. Once uploaded, the processing essentially involves reassigning all values πŸ“ from the saved file to the current values in the reactiveValues. For other inputs, such as the textInput, the corresponding updateTextInput is used to restore that value directly. This process takes place within an observeEvent πŸ”.

tl;dr

A consequence of this approach is this:

The save file retains a binary representation of your original file. As such you can recreate that file behind the scenes thus making your app no longer depend on remote access to it. Consider that anything can be represented as binary including file types like CSVs, Docx, Images, etc. 😡

Shinylive App

You can give this a try in the live app embedded below πŸ‘‡πŸ‘‡πŸ‘‡

πŸ‘‰ Use this save file I generated: Download my_saved_session.RData

When you upload it, you’ll see my progress. You’ll also see the contents of the save file, which shows both the binary representation of the source file (which you don’t have).

Thoughts

I’m not sure if I’m idealizing 😍 finally having a solution to a long-standing problem, but I’m excited 🀣. The scalability remains uncertain, so it’s probably best implemented prospectively πŸ”œ rather than retro πŸ”™.

Another consideration is the source files themselves. In some cases, storing a “copy” of the data outside its origin might violate company policy πŸ•΅. There’s definitely some gray area. In other roles, users already self-manage their own document files in various ways πŸ€” πŸ€” πŸ€”

And what about file size πŸ“ˆ ? In this example, the original XPT was 116 kb and the save file was 33.9 kb. Obviously some savings there, but where does that bottom out πŸ‹? It might depend on the format of the original file type.

Shinylive

As mentioned earlier, I’ve used this post as a two-for-one. Since this app is relatively lightweight, I employed shinylive::export to convert it to a format I can host as static content (currently on GitHub pages) πŸ€‘

It was pretty straight forward and overall looks promising. However, there are a few things to be ⚠️ mindful of: Because all of the work πŸ‘· is done in the browser it may take time for things to load βŒ›. Another key consideration is whether your app package dependencies πŸ“¦ are compatible with webR. Interactive documentation πŸ“‹ on the latter can be found here

Resources

πŸ‘‰ link to shinylive app

πŸ‘‰ github repository with code




Till next time πŸ»πŸ™ !

Matthew Kumar
Matthew Kumar
Associate Director, Lead Computational Scientist