
We're in for 2 hours of fun!
David
Senior Expert Data Scientist, Novartis
Mobile app development with {shiny}, {golem} et {shinyMobile}
Learning objectives:
{shinyMobile} introduction: components, templates, themes ...Discover progressive web apps (PWA) and create a simple example, built on top the previous part.
Learning objectives:
Appearance is critical for end user. π
Well ... it's quite complex:
Well ... it's quite complex:
β Isn't there something simpler?
π Less performance than native.
Let's be honest, there is almost nothing π.
{miniUI} exists but not really for mobile development.

Built on top of the Framework7 web framework.
f7SingleLayout(): one page app. f7TabLayout(): multi-tabs app.
2 themes:



truelle::run_app().Select Package and choose the {golem} engine.


Provide a valid package path and review project options.

Select {shinyMobile} tabs layout.

Click on the β― button or copy/paste πΈ the code to your terminal...


Congrats π! We now have a production ready {shinyMobile} project.
{shinyMobile} allows app preview (local or remote).shinyMobile::preview_mobile( url = "https://connect.thinkr.fr/shinyMobile1/", device = "iphone8")
{shinyMobile} allows app preview (local or remote).shinyMobile::preview_mobile( url = "https://connect.thinkr.fr/shinyMobile1/", device = "iphone8")
devtools::load_all().processx::process$new( "Rscript", c("-e", sprintf( "thematic::thematic_shiny(); shiny::runApp('%s', port = %s)", "./inst/app", 3434 ) ))shinyMobile::preview_mobile( url = "http://localhost:3434", device = "iphone8")

library(shiny)ui <- fluidPage( # ui logic)server <- function(input, output, session) { # server logic}shinyApp(ui, server)
library(shiny)ui <- fluidPage( # ui logic)server <- function(input, output, session) { # server logic}shinyApp(ui, server)
library(shiny)library(shinyMobile)ui <- f7Page( # shinyMobile layout functions)server <- function(input, output, session) { # server logic}shinyApp(ui, server)
f7Page() exposes an options parameter.f7Page() exposes an options parameter.devtools::load_all() and run_app().We leverage the f7TabLayout() function:
f7TabLayout(..., navbar, messagebar = NULL, panels = NULL, appbar = NULL)
f7Tabs().f7Tabs() may host multiple f7Tab().f7Navbar().We leverage the f7TabLayout() function:
f7TabLayout(..., navbar, messagebar = NULL, panels = NULL, appbar = NULL)
f7Tabs().f7Tabs() may host multiple f7Tab().f7Navbar().f7Tab() element. Hint: f7Tab(..., tabName, icon = NULL, active = FALSE, hidden = FALSE). devtools::load_all() and run_app().f7Navbar():
f7SubNavbar().f7Navbar():
f7SubNavbar().f7SubNavbar() such as:f7SubNavbar( f7Button(label = "My button"), f7Button(label = "My button"), f7Button(label = "My button"))
FALSE.FALSE. devtools::load_all() and run_app().{shinyMobile} exposes many components:
f7Card(), f7List(), f7Block(), ...f7Text(), f7Checkbox(), f7Toggle(), ...f7Notif(), f7Dialog(), f7ActionSheet(), ...f7Button(), f7TabLink(), ...f7Gauge(), f7Progress(), ...Most of them can be updated server side!
{shinyMobile} exposes many components:
f7Card(), f7List(), f7Block(), ...f7Text(), f7Checkbox(), f7Toggle(), ...f7Notif(), f7Dialog(), f7ActionSheet(), ...f7Button(), f7TabLink(), ...f7Gauge(), f7Progress(), ...Most of them can be updated server side!
f7ExpandableCard() in the previously added f7Tab().f7Button("openCard", "Click me!").<CARD_ID>:observeEvent(input$openCard, { updateF7Card(id = <CARD_ID>)})
devtools::load_all() and run_app().Progressive web apps or (PWA) improve classic web apps capabilities by obeying these three rules:
In the following we'll treat 2 and 3.

A JSON file which can be interpreted by the web browser (Chrome, Safari, Firefox, Brave, ...).
{ "name": "My Progressive Web App", "short_name": "My App", "description": "What it does!", "lang": "en-US", "start_url": "https://dgranjon.shinyapps.io/shinyMobileGolemTest/", "display": "standalone", "background_color": "#ffffff", "theme_color": "#ffffff", "icons": [ { "src": "icons/icon-144.png", "sizes": "144x144" } ], ...}
Most important fields:
www folder.Check compatibility support here.
In a Shiny app context, the manifest goes in www.
Script run in the background by the web browser.
self.addEventListener("install", (event) => { event.waitUntil( (async () => { const cache = await caches.open(CACHE_NAME); // Setting {cache: 'reload'} in the new request will ensure that the // response isn't fulfilled from the HTTP cache; i.e., it will be from // the network. await cache.add( new Request(OFFLINE_URL, { cache: "reload" }) ); await cache.add( new Request("framework7-5.7.14/css/framework7.bundle.min.css", { cache: "reload" }) ); // add other resources to cache below })() ); // Force the waiting service worker to become the active service worker. self.skipWaiting();});
Features:
Script composed of 3 steps:
Good new π!
Good new π!
{shinyMobile} is PWA ready π.
Good new π!
{shinyMobile} is PWA ready π.
Just turn on the allowPWA to TRUE in f7Page(). Under the hood, it adds all the required machinery π§.
./R/app_ui.R.golem_add_external_resources function.{shinyMobile} PWA assets).golem::add_ui_server_files().golem_add_external_resources <- function(){ #add_resource_path( # 'www', app_sys('app/www') #) tags$head( #favicon(), #bundle_resources( # path = app_sys('app/www'), # app_title = 'shinyexample' #) # Add here other external resources # for example, you can add shinyalert::useShinyalert() )}
Time consuming, need automated approach... The below script:
Time consuming, need automated approach... The below script:
# Important to target the app folder.# Only work if app is in a package.# register_service_worker is FALSE, shinyMobile already does it.# We also don't need to create web dependencies. charpente::set_pwa( "inst/app", register_service_worker = FALSE, create_dependencies = FALSE)
Time consuming, need automated approach... The below script:
# Important to target the app folder.# Only work if app is in a package.# register_service_worker is FALSE, shinyMobile already does it.# We also don't need to create web dependencies. charpente::set_pwa( "inst/app", register_service_worker = FALSE, create_dependencies = FALSE)
charpente::set_pwa("inst/app", register_service_worker = FALSE, create_dependencies = FALSE) inside your project.devtools::load_all() and run_app().Open in Browser.https://baka.thinkr.fr/<CONTAINER_ID>/rstudio/p/<RSTUDIO_TOKEN>/../inst/app/www/manifest.webmanifest start_url field.

This workshop is hosted by ThinkR on a dedicated server. If you are working locally, first deploy your app to a secure server (with HTTPS like shinyapps.io). PWA features don't work locally!!!
https://dgranjon.shinyapps.io/shinyMobileGolemTest.Generate report and wait.

We aim to see the installable property in green.

offline.html and JS/CSS files are cached (necessary for offline support).





Installed apps can be accessed under chrome://apps/
We're in for 2 hours of fun!
David
Senior Expert Data Scientist, Novartis
Keyboard shortcuts
| β, β, Pg Up, k | Go to previous slide |
| β, β, Pg Dn, Space, j | Go to next slide |
| Home | Go to first slide |
| End | Go to last slide |
| Number + Return | Go to specific slide |
| b / m / f | Toggle blackout / mirrored / fullscreen mode |
| c | Clone slideshow |
| p | Toggle presenter mode |
| t | Restart the presentation timer |
| ?, h | Toggle this help |
| o | Tile View: Overview of Slides |
| Alt + f | Fit Slides to Screen |
| Esc | Back to slideshow |

We're in for 2 hours of fun!
David
Senior Expert Data Scientist, Novartis
Mobile app development with {shiny}, {golem} et {shinyMobile}
Learning objectives:
{shinyMobile} introduction: components, templates, themes ...Discover progressive web apps (PWA) and create a simple example, built on top the previous part.
Learning objectives:
Appearance is critical for end user. π
Well ... it's quite complex:
Well ... it's quite complex:
β Isn't there something simpler?
π Less performance than native.
Let's be honest, there is almost nothing π.
{miniUI} exists but not really for mobile development.

Built on top of the Framework7 web framework.
f7SingleLayout(): one page app. f7TabLayout(): multi-tabs app.
2 themes:



truelle::run_app().Select Package and choose the {golem} engine.


Provide a valid package path and review project options.

Select {shinyMobile} tabs layout.

Click on the β― button or copy/paste πΈ the code to your terminal...


Congrats π! We now have a production ready {shinyMobile} project.
{shinyMobile} allows app preview (local or remote).shinyMobile::preview_mobile( url = "https://connect.thinkr.fr/shinyMobile1/", device = "iphone8")
{shinyMobile} allows app preview (local or remote).shinyMobile::preview_mobile( url = "https://connect.thinkr.fr/shinyMobile1/", device = "iphone8")
devtools::load_all().processx::process$new( "Rscript", c("-e", sprintf( "thematic::thematic_shiny(); shiny::runApp('%s', port = %s)", "./inst/app", 3434 ) ))shinyMobile::preview_mobile( url = "http://localhost:3434", device = "iphone8")

library(shiny)ui <- fluidPage( # ui logic)server <- function(input, output, session) { # server logic}shinyApp(ui, server)
library(shiny)ui <- fluidPage( # ui logic)server <- function(input, output, session) { # server logic}shinyApp(ui, server)
library(shiny)library(shinyMobile)ui <- f7Page( # shinyMobile layout functions)server <- function(input, output, session) { # server logic}shinyApp(ui, server)
f7Page() exposes an options parameter.f7Page() exposes an options parameter.devtools::load_all() and run_app().We leverage the f7TabLayout() function:
f7TabLayout(..., navbar, messagebar = NULL, panels = NULL, appbar = NULL)
f7Tabs().f7Tabs() may host multiple f7Tab().f7Navbar().We leverage the f7TabLayout() function:
f7TabLayout(..., navbar, messagebar = NULL, panels = NULL, appbar = NULL)
f7Tabs().f7Tabs() may host multiple f7Tab().f7Navbar().f7Tab() element. Hint: f7Tab(..., tabName, icon = NULL, active = FALSE, hidden = FALSE). devtools::load_all() and run_app().f7Navbar():
f7SubNavbar().f7Navbar():
f7SubNavbar().f7SubNavbar() such as:f7SubNavbar( f7Button(label = "My button"), f7Button(label = "My button"), f7Button(label = "My button"))
FALSE.FALSE. devtools::load_all() and run_app().{shinyMobile} exposes many components:
f7Card(), f7List(), f7Block(), ...f7Text(), f7Checkbox(), f7Toggle(), ...f7Notif(), f7Dialog(), f7ActionSheet(), ...f7Button(), f7TabLink(), ...f7Gauge(), f7Progress(), ...Most of them can be updated server side!
{shinyMobile} exposes many components:
f7Card(), f7List(), f7Block(), ...f7Text(), f7Checkbox(), f7Toggle(), ...f7Notif(), f7Dialog(), f7ActionSheet(), ...f7Button(), f7TabLink(), ...f7Gauge(), f7Progress(), ...Most of them can be updated server side!
f7ExpandableCard() in the previously added f7Tab().f7Button("openCard", "Click me!").<CARD_ID>:observeEvent(input$openCard, { updateF7Card(id = <CARD_ID>)})
devtools::load_all() and run_app().Progressive web apps or (PWA) improve classic web apps capabilities by obeying these three rules:
In the following we'll treat 2 and 3.

A JSON file which can be interpreted by the web browser (Chrome, Safari, Firefox, Brave, ...).
{ "name": "My Progressive Web App", "short_name": "My App", "description": "What it does!", "lang": "en-US", "start_url": "https://dgranjon.shinyapps.io/shinyMobileGolemTest/", "display": "standalone", "background_color": "#ffffff", "theme_color": "#ffffff", "icons": [ { "src": "icons/icon-144.png", "sizes": "144x144" } ], ...}
Most important fields:
www folder.Check compatibility support here.
In a Shiny app context, the manifest goes in www.
Script run in the background by the web browser.
self.addEventListener("install", (event) => { event.waitUntil( (async () => { const cache = await caches.open(CACHE_NAME); // Setting {cache: 'reload'} in the new request will ensure that the // response isn't fulfilled from the HTTP cache; i.e., it will be from // the network. await cache.add( new Request(OFFLINE_URL, { cache: "reload" }) ); await cache.add( new Request("framework7-5.7.14/css/framework7.bundle.min.css", { cache: "reload" }) ); // add other resources to cache below })() ); // Force the waiting service worker to become the active service worker. self.skipWaiting();});
Features:
Script composed of 3 steps:
Good new π!
Good new π!
{shinyMobile} is PWA ready π.
Good new π!
{shinyMobile} is PWA ready π.
Just turn on the allowPWA to TRUE in f7Page(). Under the hood, it adds all the required machinery π§.
./R/app_ui.R.golem_add_external_resources function.{shinyMobile} PWA assets).golem::add_ui_server_files().golem_add_external_resources <- function(){ #add_resource_path( # 'www', app_sys('app/www') #) tags$head( #favicon(), #bundle_resources( # path = app_sys('app/www'), # app_title = 'shinyexample' #) # Add here other external resources # for example, you can add shinyalert::useShinyalert() )}
Time consuming, need automated approach... The below script:
Time consuming, need automated approach... The below script:
# Important to target the app folder.# Only work if app is in a package.# register_service_worker is FALSE, shinyMobile already does it.# We also don't need to create web dependencies. charpente::set_pwa( "inst/app", register_service_worker = FALSE, create_dependencies = FALSE)
Time consuming, need automated approach... The below script:
# Important to target the app folder.# Only work if app is in a package.# register_service_worker is FALSE, shinyMobile already does it.# We also don't need to create web dependencies. charpente::set_pwa( "inst/app", register_service_worker = FALSE, create_dependencies = FALSE)
charpente::set_pwa("inst/app", register_service_worker = FALSE, create_dependencies = FALSE) inside your project.devtools::load_all() and run_app().Open in Browser.https://baka.thinkr.fr/<CONTAINER_ID>/rstudio/p/<RSTUDIO_TOKEN>/../inst/app/www/manifest.webmanifest start_url field.

This workshop is hosted by ThinkR on a dedicated server. If you are working locally, first deploy your app to a secure server (with HTTPS like shinyapps.io). PWA features don't work locally!!!
https://dgranjon.shinyapps.io/shinyMobileGolemTest.Generate report and wait.

We aim to see the installable property in green.

offline.html and JS/CSS files are cached (necessary for offline support).





Installed apps can be accessed under chrome://apps/