3

I have a Shiny App (actually an interactive R Markdown report) that that I want to format depending on whether the user is on mobile or not. I found this blog post by g3rv4 which describes how to test for this, but I haven't been able to get it to work in the example app below.

https://g3rv4.com/2017/08/shiny-detect-mobile-browsers

I am a modeller, not a programmer, so there's probably something about the Javascript that I am doing wrong. I am not getting an error, but I am not getting any output from textOutput('isItMobile').

# shiny example from
# https://shiny.rstudio.com/tutorial/written-tutorial/lesson1/
# mobile detect code from
# https://g3rv4.com/2017/08/shiny-detect-mobile-browsers
library(shiny)

onStart <- function(input, output) {

  ### function to detect mobile ####
  mobileDetect <- function(inputId, value = 0) {
    tagList(
      singleton(tags$head(tags$script(src = "js/mobile.js"))),
      tags$input(id = inputId,
                 class = "mobile-element",
                 type = "hidden")
    )
  }

}

# Define UI for app that draws a histogram ----
ui <- fluidPage(

  # App title ----
  titlePanel("Hello Shiny!"),

  mobileDetect('isMobile'),
  textOutput('isItMobile'),

  # Sidebar layout with input and output definitions ----
  sidebarLayout(

    # Sidebar panel for inputs ----
    sidebarPanel(

      # Input: Slider for the number of bins ----
      sliderInput(inputId = "bins",
                  label = "Number of bins:",
                  min = 1,
                  max = 50,
                  value = 30)

    ),

    # Main panel for displaying outputs ----
    mainPanel(

      # Output: Histogram ----
      plotOutput(outputId = "distPlot")

    )
  )
)

# Define server logic required to draw a histogram ----
server <- function(input, output) {

  output$isItMobile <- renderText({ 
    ifelse(input$isMobile, "You are on a mobile device", "You are not on a mobile device")
  })

  # Histogram of the Old Faithful Geyser Data ----
  output$distPlot <- renderPlot({

    x    <- faithful$waiting
    bins <- seq(min(x), max(x), length.out = input$bins + 1)

    hist(x, breaks = bins, col = "#75AADB", border = "white",
         xlab = "Waiting time to next eruption (in mins)",
         main = "Histogram of waiting times")

  })

}

# this is how you run it
print('Running Simple Shiny App - Hit ESC to quit.')
shinyApp(ui = ui, server = server, onStart = onStart)

Here is the www/js/mobile.js file:

var isMobileBinding = new Shiny.InputBinding();
$.extend(isMobileBinding, {
  find: function(scope) {
    return $(scope).find(".mobile-element");
    callback();
  },
  getValue: function(el) {
    return /((iPhone)|(iPod)|(iPad)|(Android)|(BlackBerry))/.test(navigator.userAgent)
  },
  setValue: function(el, value) {
  },
  subscribe: function(el, callback) {
  },
  unsubscribe: function(el) {
  }
});

Shiny.inputBindings.register(isMobileBinding);
Simon Woodward
  • 1,663
  • 1
  • 9
  • 20
  • 1
    Have you considered using a package that provides a framework to do this built-in? I would suggest `flexdashboard` for simple requirements, especially considering you say it is for an interactive `rmarkdown` report. If you need a lot more in features, take a look at `shinydashboard`, though that has a much steeper learning curve. – Kevin Arseneau Oct 18 '17 at 23:50
  • Thanks! I'll look into it. – Simon Woodward Oct 19 '17 at 00:45
  • Hi @SimonWoodward, did you ever manage to fix this? I'm having the same issue – Sharma Jun 19 '18 at 11:29
  • Hi @Sharma no I did not get this working. – Simon Woodward Jun 20 '18 at 19:15

1 Answers1

3

Here's a way to detect a (small) mobile device based on the screen size. Note this deliberately excludes tablets with a larger screen.

The following snippet checks if the screen size is below 768px and sends the result to the Shiny server using onInputChange as an input named is_mobile_device. The check is only done once when the page loads and the Shiny UI has finished loading.

Put this in a JS file and include it in your UI (e.g. like it is done in the question using tags$script):

$(document).on('shiny:sessioninitialized', function (e) {
  var mobile = window.matchMedia("only screen and (max-width: 768px)").matches;
  Shiny.onInputChange('is_mobile_device', mobile);
});

Browser support: http://caniuse.com/#feat=matchmedia

In your Shiny server function you can define a reactive to retrieve the value:

server <- function(input, output, session) {
    is_mobile_device <- reactive(isTRUE(input$is_mobile_device))
    # ...
}

For other ways to detect mobile devices on the JavaScript side (e.g. based on user agent to also include tablets), check the excellent answers here:

Simon G.
  • 330
  • 4
  • 9