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/