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:
- They are usually tied to remote raw data that must be accessed with a live connection π²
- 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:
- Start by uploading a fresh file π
- 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
π github repository with code
Till next time π»π !