shinyMobile icon indicating copy to clipboard operation
shinyMobile copied to clipboard

App disconnects when lock screen activated

Open dewalex opened this issue 3 years ago • 20 comments

This may be a problem with Shinyapps in general, but if my phone sleeps and activates the lock screen, or if I manually activate it, the app will disconnect from Shiny Server after just about 15 -30 seconds.

Is there a current method, or could there be an enhancement, to make the app run as a background task when either the lock screen is activated or if the user opens another app while the Shinyapp is open?

dewalex avatar Jul 29 '20 13:07 dewalex

I have the same issue, and the same question.

Tixierae avatar Jul 29 '20 16:07 Tixierae

It's not really due to {shinyMobile} but rather how shiny servers work. After a given amount of idle time, your session is closed.

DivadNojnarg avatar Oct 26 '20 10:10 DivadNojnarg

So, is there a way to keep a process ticking, and put that in the app that will keep the app from going idle when a mobile screen is locked? Having that feature would really step up the game for apps made using this package.

dewalex avatar Oct 26 '20 15:10 dewalex

I think the key to this would be allowing the app to work when the screen is locked, or when the app screen is minimized and the user is looking at another app on their phone.
In my own app, if the user stays on the first tab, the results are updated every 15 seconds, so the app stays alive, never having more than 15 seconds of idle time. However, when the screen is locked, this does not occur and results in the app being disconnected from the server.
The other thing to note is that there has to be two different idle time settings. When an app is open on a screen that doesn't go to sleep, it will take 15 minutes to disconnect from the server, but if you lock your screen the app will disconnect if you do not get back to it within 30 seconds. I'm wondering if there is a way to extend the latter process somehow.

dewalex avatar Oct 26 '20 15:10 dewalex

I concur to everything that was said. It would really be a huge improvement if the app could stay alive when the screen is locked. Right now, I am using https://github.com/richtr/NoSleep.js/ to prevent the screen from locking (it plays an empty video in the background), but it is only a hack, not a true solution. And it does not work when the user is looking at another tab/app on their phone.

Tixierae avatar Oct 26 '20 20:10 Tixierae

Can you show me how you are sticking that in your script? Just in the UI as tags$script() and saving it in your project folder with your .server and .ui?

dewalex avatar Oct 26 '20 20:10 dewalex

I think this could be done in a none hacky way by using shiny's events. But 1) as @DivadNojnarg stated it's outside of the scope of shinyMobile and 2) would probably be rather dangerous as you run the risk of burning all your minutes on shiny apps and blocking R' single thread.

JohnCoene avatar Oct 26 '20 21:10 JohnCoene

@JohnCoene Using up my Active Hours isn't an issue at this time. I have an app where the user can navigate to certain areas on a map using their phone's location. The app has a login, so it is really frustrating if they switch over to check a text and then have to fully log back into the app to get back to the map after the server disconnects them.
How would you be able to keep an app running in the background using events?

dewalex avatar Oct 26 '20 21:10 dewalex

@dewalex : have you tried playing with session$allowReconnect (server side)? There are other ways client side (JS) with Shiny.shinyapp.scheduleReconnect(delay_in_ms) or Shiny.shinyapp.reconnect(). This has to be put on the client and does not depend on any shiny event since the socket connection occurs before shiny:connected and shiny:sessioninitialized (those events are triggered after the client connection). I can’t promise it will work on shiny apps.io where the infrastructure involves load balancing across workers, which is not the case on a open source shiny server.

DivadNojnarg avatar Oct 27 '20 06:10 DivadNojnarg

As a starting point. A click on the first button crashes the app. Clicking on the reconnect button reconnect the client. Inputs values are properly restored since they were stored in the browser.

if you uncomment the server code session$allowReconnect("force"), you don't need the client reconnection, that is Shiny.shinyapp.reconnect(). I set session$allowReconnect("force") locally but you would need session$allowReconnect(TRUE) on a server environment.

library(shiny)
library(shinyMobile)

shiny::shinyApp(
  ui = f7Page(
    color = "pink",
    title = "Floating action buttons",
    f7SingleLayout(
      navbar = f7Navbar(title = "f7Fabs"),
      tags$head(
        tags$script(
          "$(function() {
            // crash the socket
            $('#btn1').on('click', function(event) {
              Shiny.shinyapp.$sendMsg('plop');
            });
          });
        "
        )
      ),
      tags$a(
        type = "button",
        class = "f7-action-button",
        onclick = "Shiny.shinyapp.reconnect();",
        "Reconnect"
      ),
      
      f7Fabs(
        position = "center-center",
        color = "purple",
        sideOpen = "center",
        lapply(1:4, function(i) f7Fab(paste0("btn", i), i))
      ),
      lapply(1:4, function(i) verbatimTextOutput(paste0("res", i))),
      
    )
  ),
  server = function(input, output, session) {
    #session$allowReconnect("force")
    lapply(1:4, function(i) {
      output[[paste0("res", i)]] <- renderPrint(input[[paste0("btn", i)]])
    })
  }
)

Edit: tested here -> https://dgranjon.shinyapps.io/socket-reconnect. Does not work. shinyapps.io is adding some stuff that allows automatic reconnection and does not keep input history.

DivadNojnarg avatar Oct 27 '20 09:10 DivadNojnarg

@dewalex please see MRE below. A public live example can be found here: https://safetyapp.shinyapps.io/wavelock_test/

ui.R

shinyUI(
    fluidPage(
        includeHTML('./wavelock.html') # originally taken from: https://github.com/richtr/NoSleep.js/blob/master/example/index.html
    )
)

server.R

function(input, output) {}

wavelock.html -> https://www.dropbox.com/s/0hzztpwjuxeum9m/wavelock.html?dl=0

Tixierae avatar Oct 27 '20 09:10 Tixierae

@DivadNojnarg Thanks so much for looking into this.
If shinyapps .io is adding something that purges histories and makes it impossible to reconnect, would our next option be to make the app run as a background process when the lock screen is engaged (like most apps do)? Is there a way to through JS or something else to keep the app running as normal when the screen is locked or the user switches active screens?

dewalex avatar Oct 27 '20 13:10 dewalex

@Tixierae Thank you!

dewalex avatar Oct 27 '20 13:10 dewalex

@dewalex Uhm, I found a way. Below is the JS part I added. I first check if I am on rstudio connect, shiny server pro, shinyapps.io, ... then if yes, each time I am disconnected shiny:disconnected, I remove the shiny server client js custom reconnect dialog and replace it by the builtin Shiny.showReconnectDialog. It prevents the app from reloading.

var workerId = $('base').attr('href');
            // ensure that this code does not locally
            if (typeof workerId != 'undefined') {
              $(document).on('shiny:disconnected', function(event) {
                $('#ss-connect-dialog').hide();
                $('#ss-overlay').hide();
                Shiny.showReconnectDialog();
              });
            }
library(shiny)
library(shinyMobile)

shinyApp(
  ui = f7Page(
    color = "pink",
    title = "Floating action buttons",
    f7SingleLayout(
      navbar = f7Navbar(title = "f7Fabs"),
      tags$head(
        tags$script(
          "$(function() {
            // crash the socket
            $('#btn1').on('click', function(event) {
              Shiny.shinyapp.$sendMsg('plop');
            });

            var workerId = $('base').attr('href');
            // ensure that this code does not locally
            if (typeof workerId != 'undefined') {
              $(document).on('shiny:disconnected', function(event) {
                // remove shiny server stuff
                $('#ss-connect-dialog').hide();
                $('#ss-overlay').hide();
                // use Shiny internal tools
                Shiny.showReconnectDialog();
              });
            }
          });
        "
        )
      ),
      tags$a(
        type = "button",
        class = "f7-action-button",
        onclick = "Shiny.shinyapp.reconnect();",
        "Reconnect"
      ),

      f7Fabs(
        position = "center-center",
        color = "purple",
        sideOpen = "center",
        lapply(1:4, function(i) f7Fab(paste0("btn", i), i))
      ),
      lapply(1:4, function(i) verbatimTextOutput(paste0("res", i))),

    )
  ),
  server = function(input, output, session) {
    #session$allowReconnect("force")
    lapply(1:4, function(i) {
      output[[paste0("res", i)]] <- renderPrint(input[[paste0("btn", i)]])
    })
  }
)

Deployed here: you can try on your phone, I made it PWA compatible.

I you're ok with this, I'll add it to {shinyMobile} core so that you don't have to include this JS code in your app...

On the other hand, we should also give the ability to reload (there should be 2 buttons, reconnect and reload) in case people really want to reload the app. With the code I gave you, the only way to reset the app (when in fullscreen PWA mode) is to kill the app in the task manager, which is not necessarily what we want.

DivadNojnarg avatar Oct 27 '20 17:10 DivadNojnarg

@DivadNojnarg Thanks for putting that together! It looks like it will be really useful. I definitely agree with putting the option for reconnect/reload. I see that you already added it to the deployed example.

I added your JS to my code and it is working intermittently for some reason (sometimes just the normal Shinyapps.io disconnected message comes up with the option to reload). When I do get your message and "try now" button to reconnect, it reconnects. However, I have a login dialog to have users log in and it makes you reenter your login credentials and then when you get back to the map, it will take you to the tab you were on, but only have part of the information that was plotted on the map previously. I think there may be too much going on in my app for this to work.

I definitely think it should be added to the core, but if there is something that could make the app run as a background task, that would be something else really useful. For example, if I had the user record their path and the app sent a coordinate to a database every 15 seconds, the user would be able to lock their screen and continue recording their trail.
For my current uses, it would just allow for the lock screen to come on without having the user have to log back in again, which would be a tremendous help.

dewalex avatar Oct 28 '20 20:10 dewalex

@DivadNojnarg When you added this to the core, did you set any command to choose to use it, or customize the message, etc?

Also, has there been any additional thought on how to keep the shinyapp running in the background when the screen is the locked? (ive noticed that the reconnect doesn't work when I have a login dialog or waiter screens on each module).

dewalex avatar Feb 10 '21 23:02 dewalex

@dewalex I changed the icon and message, and kept only one button to refresh the page by slightly editing the inst/shinyMobile-0.8.0.9000/js/shinyMobile.js file (see here: https://github.com/Tixierae/shinyMobile/blob/46c5a7f458e176c12a3ebb7dce95f4c29ceaf594/inst/shinyMobile-0.8.0.9000/js/shinyMobile.js#L55)

Here is how it looks:

sleeping_shinymobile

A bit better IMO, except that I could not figure out how to center the one button I kept (if anyone had a clue, I'd appreciate it).

But I agree, it would be better to be able to pass the icon and message as parameters.

For the second part, I am using wavelock for now. It keeps the mobile device always awake. So, I don't have to figure out how to reconnect since the app never disconnects due to the mobile device (only due to shinyapps.io, which is 30 min after the last user interaction). When the latter happens, I just offer the user the option to refresh the page.

Tixierae avatar Feb 11 '21 10:02 Tixierae

@Tixierae Thanks for sending that, I'll take a look!

I put wavelock on my app too after you suggested it, which does help, however since people are using it for mapping, they will be manually locking their screen sometimes and switching to other apps.

dewalex avatar Feb 12 '21 16:02 dewalex

Ah yes, you're right, wavelock does not address the 'app switching' issue. Then, yes, we really need a way to keep it running in the background...

Tixierae avatar Feb 12 '21 16:02 Tixierae

...thinking more about the issue of locked screens and users switching to other apps, does anyone have any experience with making Shiny apps standalone apps?

https://stackoverflow.com/questions/33513544/deploying-r-shiny-app-as-a-standalone-application

That seems like it would maybe solve the issues of the above (and would also open up possibilities for things like local bluetooth connections that read off the machine's port, right?), but how is this actually done? Does R have to be downloaded and run in the background somehow (which would not work for mobiles), or are there solutions that would be mobile friendly?

dewalex avatar Mar 02 '21 02:03 dewalex