delight-im / Android-DDP

[UNMAINTAINED] Meteor's Distributed Data Protocol (DDP) for clients on Android
Apache License 2.0
274 stars 54 forks source link

Handling app sleep / screen lock / Activity pause #74

Open rjhllr opened 8 years ago

rjhllr commented 8 years ago

Good day to all of you,

after searching the repo for certain keywords (sleep, onResume, lock..) I didn't come up with an issue talking about this topic and it's not clear from the doc either. I've written an app that consumes and manipulates data on a Meteor server. Now this app is expected to be used on-and-off over a period of some hours. This, of course, includes users locking their screen and/or switching to a different app from time to time.

I've done a test where I add a new document to a collection and update an existing one after having locked the screen. When I unlock the screen again, I come back to my activity which remains unchanged. When I then add another document or update one, all documents update accordingly. From this I deduce that the app does not know about changes that happen when the Activity is paused, which is what I'd expect.

Taking a guess I'd say that I have to implement some sort of "refreshing" in the onResume method of the activity. I have already tried calling disconnect/reconnect there, which does not work (probably because of the login which is needed for each and every method). I'm sure there must be a better solution though.

Thank you!

ocram commented 8 years ago

Thanks for your question and thanks for doing the research in the old issues first!

There are two similar issues that are certainly hard to find because they use other terms:

We definitely need to know more about your Activity lifecycle. Where do you connect to the server, where do you disconnect again? If you remove all listeners in onPause, for example, you cannot get any new updates, of course.

Have you thought about using this library from a background Service or IntentService?

rjhllr commented 8 years ago

Thank you for the reply. I have read the referenced issues now. I'm starting to think that my issue however is more of a question on how to best implement this library, as you mentioned in your last question.

I have a basic app which consists of a LoginActivity (separate Activity), an OverviewActivity and multiple fragments (e.g. ListInvoicesFragment). The Meteor connection is created in the LoginActivity and is persisted in the application context for global accessibility. The OverviewActivity is instantiated after a successful login. This class implements MeteorCallback. It creates Meteor subscriptions in onCreate and does not override onPause or onResume, so those are still default.

I understand that a paused Activity, by definition, can not receive the changed data over the network. One possible implementation would be a background Service, which would also enable me to do some more interesting things in the future (i.e. push notifications over DDP) but I thought "refreshing" the data on onResume would be a good first start without going down the road of a background service possibly straining the battery more than necessary.

To sum it up, do you think that when an application needs to be used with interruptions (screen locks, app switches..) using a Service is the best option for us?

Thank you again, much appreciate the help and this lib!

ocram commented 8 years ago

I'm starting to think that my issue however is more of a question on how to best implement this library, as you mentioned in your last question.

I think so, too. And that question hasn't really been answered yet, neither in those related issues.

The Meteor connection is created in the LoginActivity and is persisted in the application context for global accessibility.

Does this mean that the reference to the Meteor instance is kept in a subclass of Application?

First, you should check why the subscription data is outdated when the Activity is resumed. That's the main problem, right? Does the connection go away when the Activity is paused? Or is the connection still alive but the callbacks have been removed? (Where do you actually remove the callbacks?)

A background Service could (perhaps) be a good idea because it may fit your use case really well. Given that questions about sleeping apps are asked more often, and nearly everybody uses Meteor across multiple Activity instances, maybe we should include an improved singleton or the Service pattern as the main use case in the README as well.

Regarding the Service class, you can bind to such instances from multiple Activity instances and the Service only goes away when the last Activity unbinds. That sounds useful, I think.

But I'm not sure if the Service pattern really helps with a sleeping app. It would be annoying to switch to a Service implementation and having the same problem with your resumed Activity, right?

So let's first check why the data becomes outdated at all (see above) and whether we can fix the normal access patterns.

And thanks for your appreciation!

rjhllr commented 8 years ago

Does this mean that the reference to the Meteor instance is kept in a subclass of Application?

Indeed, it is. I have a custom class for the application's context extending Application. The connection also does not "go away" as you put it. The same connection is there after resuming the app, as I do not need to re-establish the connection and log-in again (the user is still authenticated, which is why I think it is the same connection).

What struck me as odd is that when I change more of the data (e.g. add a new record or modify the record once more), the data automatically syncs again. That is, even when an unrelated event occurs (changing data instead of adding, for example). For example:

  1. I lock the screen
  2. I add two invoices (from another client)
  3. I unlock the screen. The data is now out of sync and two invoices are missing.
  4. I now change any invoice (from another client)

after this, the data of the two added invoices shows up on the client as well. It's almost as if the callbacks are delayed when the app is paused. If you can provide me with a hint on how to debug this for sure I'd be glad to do so!

I do not currently remove the callbacks anywhere. I will be removing them by user interaction only (logging out or explicitly exiting the app) in the future.

ocram commented 8 years ago

That all sounds good! No reason to assume a bug in your application design.

I do not currently remove the callbacks anywhere. I will be removing them by user interaction only (logging out or explicitly exiting the app) in the future.

That sounds good as well. Since you can't detect when the whole app is killed by Android, anyway, this is probably the best solution.

Thanks for your debugging work so far. The fact that all data sets are sync'ed again as soon as you perform any (unrelated) action is very good news. And, of course, some little bad news since we don't know yet why this is happening. But that should be much easier to debug and resolve.

What you could do is:

Please log everything and inspect the places before and after the screen goes off (and on again) and when your perform the (unrelated) action that makes the sync work again.

rjhllr commented 8 years ago

Okay, some more input from the test bench here. I have added Log outputs and breakpoints to the overridden MeteorCallback methods. None of them fire when I lock or unlock the screen.

One more observation I made, just since it seems curious: the app does notice a dropped connection, even when the screen is locked. Both onException and onDisconnect are called as you would expect and custom UI updates (displaying a material design Snackbar in my case) are triggered and shown when I unlock the screen again. I somehow found it interesting that the application does not receive incoming data but notices the loss of connection. I tested this by just shutting the Meteor application server down.

Calling a non-defined method on the server does not sync the data sadly. I will continue my try at debugging, possibly by triggering a minor update to the dataset myself onResume. If that fails I'd try a background Service and if I do find the time shortly I could also create a small example to attach to this issue, just so that it is more reproducible.

ocram commented 8 years ago

Thank you!

I have added Log outputs and breakpoints to the overridden MeteorCallback methods. None of them fire when I lock or unlock the screen. [...] the app does notice a dropped connection, even when the screen is locked. Both onException and onDisconnect are called as you would expect

Now do you get onDisconnect or onException in MeteorCallback when you lock/unlock or not?

If these callbacks were called, that would be good news again :) The exception could even have a reason and details included.

And then, why does the connection come back again? Is this the built-in reconnect attempt? Or is it due to some other reason? Do you get onConnect again after this?

We have to find out why the connection comes back again and what triggers the sync to work again. As you wrote in one of the last comments, sync seems to start working again when you write some (unrelated) data. Some other activity, such as calling a method, does not work, as you noted.

rjhllr commented 8 years ago

I do not get onDisconnect or onException callbacks when all I do is lock or unlock the screen, the test case outlined above. I do however get callbacks on those events when I lock the screen and then shut down the Meteor server. Which struck me as odd since I thought that the paused Activity, which does not notice changed data, should also not notice a dropped connection. Sorry for causing confusion by mentioning this, potentially irrelevant, side fact. I will investigate further on this issue tomorrow and will try to proceed as I outlined above, taking into account what you just said as well.

ocram commented 8 years ago

Thanks, that was not irrelevant at all. Got it now :)

Actually, it's quite important, isn't it? The explanation for the difference between lock -> unlock and lock -> shutdown must be that the connection is not lost at all in the first case. Do you agree?

One could verify this by calling some method after locking the screen (e.g. scheduling a task 5 seconds after onPause). That method call could log something on the server. If the method call succeeds even after the screen has been locked, the connection must be alive.

Sorry for this problem which requires quite some amount of debugging, apparently.

rjhllr commented 8 years ago

I have just done the proposed quick test. I've defined a Meteor method on the server which just echoes something to the console. Then I have set up a delayed Handler/Runnable in the onPause which logs to the adb console and calls the Meteor method, 5 seconds after the screen was locked. Results are that the method call goes through fine. We can thus say with certainty that the connection remains alive with the screen being locked, but stops receiving data once the screen is locked.

ocram commented 8 years ago

Thank you for testing this!

Could you please add Meteor.setLoggingEnabled(true); somewhere before the connection is established? For example, in your Application subclass's onCreate method?

That should help with debugging. Usually, you should even be able to see ping and pong messages being exchanged then. It could be interesting to know whether these messages are still exchanged when the screen goes off.

If we tried to sum up what we have found out so far, this would be that everything works fine when the screen goes off, except that data updates are not sent anymore until the client modifies some data itself again. Right?

rjhllr commented 8 years ago

It will take me a few more days to get back on this to you. Sorry for the delay and thank you for the support thus far.

ocram commented 8 years ago

No problem. Thanks a lot for helping debug this problem!

Just get back to this issue when you have some new information :)

rjhllr commented 8 years ago

After some more debugging I once more lean towards the side of a bug in my application design. I will have to do some more testing in order to confirm this. It could be that the issue I reported here is indeed more of a question than it is a bug. What I can tell you however is the following: ping and pong messages come through as intended. The timing changes a bit (~15s) when locking the screen:

22:26:01.352 ping
22:26:31.412 ping
22:27:01.420 ping
22:27:31.428 ping

pause app at 22:27:55.868

22:28:16.440 ping (locked screen)
22:28:46.452 ping (locked screen)

but nothing out of the ordinary and nothing that would let me suspect a flaw in this library, am I right? Also when changing data from another client the changed data arrives even at a "locked screen" instance of this Android client, which leads to me believe that maybe the problem I'm facing is as easy as some UI updating code missing in my codebase. More on that tomorrow when I have a full evening of free time for debugging this issue :)

thank you once again for the work you put in here!

ocram commented 8 years ago

Thanks, @rjhllr!

That does indeed look good and the pings and pongs seem to go through. So one might think it's a bug in your application code.

But a few weeks ago, you came up with the following findings, didn't you?

We can thus say with certainty that the connection remains alive with the screen being locked, but stops receiving data once the screen is locked.