Reporting network errors to users - ios

NOTE: This is more of a "philosophical" question than a technical one.
So, I'm writing an iPad app which syncs its data to a backend which I also wrote. The sync works well, on a good network like the one at my apartment.
But in a slow or otherwise mediocre network like the public one in a Starbucks or my office it has a tendency to error out. In my testing, when it fails (albeit, infrequently) on these mediocre networks it's a timeout or "could not connect to the server" scenario, and the second attempt has consistently succeeded, resulting in a successful sync.
So my question is about how or whether to report these errors to the user. Right now I quietly report all errors to the user ( using an unobtrusive error mark, not a modal dialog ), but it's annoying in these scenarios because the next sync (has so far) always succeeded.
For some context: My app syncs in the background, continuously, as local changes are spotted and pushed to the server and as a long-polling background thread watches for changes on the server and pulls them downstream to the app.
My thought is that when I sync, I should try (for example) 3 times, breaking out on the first successful attempt. Only if all three attempts fail should I report an error to the user.
Is this a reasonable idea? Or is this a terrible idea?
Thanks,

Related

Vaadin: UI Detached exception after ~80 minutes of inactivity

I wrote myself a server monitoring utility using Vaadin 18.0.2 for the UI. This works very nicely execpt for one major issue:
The monitoring is meant to become part of our "team-screen", i.e. a big screen displaying team news, build results, server status, etc.). As such the application has be able to run for longer periods without any user interaction.
The application once per minute queries a bunch of servers re. livelihood and response times and then updates a corresponding result screen. For some reason it always crashes after pretty reproducibly 80 minutes with a com.vaadin.flow.component.UIDetachedException:
com.vaadin.flow.component.UIDetachedException: null
at com.vaadin.flow.component.UI.handleAccessDetach(UI.java:412)
at com.vaadin.flow.component.UI.access(UI.java:512)
at com.vaadin.flow.component.UI.access(UI.java:499)
...
Once this happens all subsequent UI updates have the same issue, i.e. the display "freezes".
The above code is called by a listener which always first requests a Vaadin UI handle and then updates the screen. While it keeps getting such a UI there obviously something's wrong with it after these 80 minutes and then - while the application's monitoring core continues to run - all UI updates are impossible. Neither reloading the page in the browser or even terminating and restarting the browser helps. The application's web-interface is "dead" after that and only an application restart can revive it. This is of course a complete killer or "no-go" for this kind of application. :-(
I suspect that there is some session timeout or UI object duration limit being reached but I haven't encountered any setting to control that so far. I also found no docs mentioning that some token or handle or similar needs to be regularly refreshed or so.
What I specifically don't understand is, why a browser page reload or even a browser restart doesn't help here. One doesn't get a login-in screen as one might expect if some token or session cookie would have expired or so, but the application's web interface is completely "dead" after that time. One doesnt even get any response anymore from the server but the browser requests just time out...
Any hint or guidance what could be the cause for these exceptions or what mechanism is behind this behavior?

How to delete a CKRecord if not yet finished creating due to bad network connection?

I am currently working on an iPhone App which utilizes CloudKit for syncing the app data between the users devices. I use CoreData as my local cache to make sure that the app stays usable when the device is offline.
The Problem
During the development I came across an issue concerning the sync behavior when the device is offline. Let me explain by giving an example:
The user creates a Person (one entity I'm dealing with)
The person is saved to the local cache → CoreData
A CKRecord is created and set up to match the data of the locally cached entity
To save the CKRecord to CloudKit, a CKModifyRecordsOperation is created and set up with all the completion blocks and properties needed
The CKModifyRecordsOperation is added to the database
If the device has a working network connection the CKRecord is created as desired. But when the operation fails due to a bad network connection the CKRecord is not created.
Let's assume the device stays offline and the user decides to delete the person again. This is no problem for the data locally cached on the device. But due to the fact that the local cache has no CKRecordID associated, no CKModifyRecordsOperation can be created to delete the CKRecord in the cloud.
Now the device establishes a network connection and is online again. So now the CKModifyRecordsOperation to create the Person-Entity is executed. This results in the local cache and the cloud being out of sync.
I thought of fixing this issue by keeping track of pending operations concerning a Person-Entity. If the person gets deleted the pending operations get cancelled.
Unfortunately I could not get this running. So I would appreciate some advice if I'm on the right track!
Thank you!
Try adjusting the .qualityOfService attribute on the operation. Per Apple Docs at https://developer.apple.com/library/content/documentation/Performance/Conceptual/EnergyGuide-iOS/PrioritizeWorkWithQoS.html#//apple_ref/doc/uid/TP40015243-CH39:
User-interactive: Work that is interacting with the user, such as
operating on the main thread, refreshing the user interface, or
performing animations. If the work doesn’t happen quickly, the user
interface may appear frozen. Focuses on responsiveness and
performance. Work is virtually instantaneous.
User-initiated Work that the user has initiated and requires
immediate results, such as opening a saved document or performing an
action when the user clicks something in the user interface. The work
is required in order to continue user interaction. Focuses on
responsiveness and performance. Work is nearly instantaneous, such
as a few seconds or less.
Utility Work that may take some time to complete and doesn’t require an immediate result, such as downloading or importing data.
Utility tasks typically have a progress bar that is visible to the
user. Focuses on providing a balance between responsiveness,
performance, and energy efficiency. Work takes a few seconds to a
few minutes.
Background Work that operates in the background and isn’t visible to
the user, such as indexing, synchronizing, and backups. Focuses on
energy efficiency. Work takes significant time, such as minutes or
hours.
The page also says the default is between user-initiated and Utility.
According to this discussion on the Apple dev forums https://forums.developer.apple.com/thread/20047, users reported not receiving errors for queries that failed while being offline and, like you're seeing, it seems those queries were actually persisted and tried again when connection was restored. Users in that thread reported that changing the QoS parameter to User-initiated caused an error to be returned immediately when the operation couldn't be completed due to no network.
It also appears that when the operation is being persisted, the operation's longLivedOperationWasPersistedBlock will be called.
So, option 1: try adjusting the QoS value to a "higher" (more urgent) value on the operation, which should cause errors to be thrown rather than queuing the operation for later.
Option 2: try adding the longLivedOperationWasPersistedBlock. If it fires, you could try canceling the operation in that block, and displaying a "no network" error to the user.

Unique Realm container objects

I implemented real time sync following Realm's tasks demo app.
There a dummy container is used, to hold a List with the models.
The demo app doesn't seem to support offline usage.
I wondered what happens when, given this setup, I start the app on an online as well as an offline device and then go online with the offline device.
My initial expectation was that I'd end with 2 containers (which would be an invalid state), but when I tested surprisingly there was only 1 container at the end.
But sometimes I get 2 containers and haven't been able to identify what causes this.
The question then is, how does this exactly work? I assume the reason that the container is normally not duplicated when I sync the offline device for the first time is that it's handled as the same object, maybe because it doesn't have a primary key or something? But then why is it sometimes duplicated? And what would be the best practice here? Do I maybe have to use a primary key or check after connecting if there's duplication and if yes do a manual merge of the containers?
At the moment, Realm Tasks merely checks if the default Realm is empty before it tries to add a new base list container object. If the synchronization process hasn't completed by the time this check occurs, it's reasonable that a second container would be created. When testing the app on a local network, this usually isn't a problem since the download speeds are so fast, but we definitely should test this a bit more thoroughly.
Adding a primary key will definitely help since it means that if a second list is created locally, it will get merged with the version that comes down from the server.
We've recently been focusing on the 'on-boarding' process when a second device connects to a user's Realm Mobile Platform account via the new progress notification system. A more logical approach would be to wait for the synchronization to complete the initial download after logging in, and then checking for the presence of the objects. Once the documentation is complete, we'll most likely be revamping how Realm Tasks handles this.
The demo app (as well as the Realm Mobile Platform) does support offline, but only after the user has logged in for the first time (which is when these container objects are initially generated). After that time, the apps can be used offline, and any changes done in that interim are synchronized the next time it comes online.
We're planning on building 'anonymous user' feature where a user can start using the app straight away (even offline) and then any changes they made before they log in (due to them being offline) are then transferred to the user account after they do so.

Bootstrapping data at application startup with Simperium

As someone that experienced the pain of iCloud while trying to prototype iCloud enabling one of our CoreData apps, Simperium looks very promising, but I'm interested in seeing how it handles some of the sharp edges.
One issue I came across was how to gracefully handle bootstrapping data when the application starts up. The first time a user launches our app, we will load some default data into our CoreData database. If a user launches the app first on the iPhone and then later on the iPad, they will end up getting the bootstrap data duplicated on both devices because of syncing. With iCloud, the solution was to hook into the iCloud merge process.
How would I handle this with Simperium?
There are at least a couple ways to do this.
You can hardcode the simperiumKey for each seeded object. For example, in a notes app, if every new user gets a welcome note, you can locally create that note with the simperiumKey of welcomeNote. This will ensure that only one welcome note will ever exist in that user's account (on any device). With this approach, there can be some redundant data transfer, so it's best if there's not a large amount of seeded data. On the other hand, this approach is good if you want data to be immediately available to new users even if they're offline when they first launch your app.
With Simperium, you also have the option to use a server process. You can seed new user accounts with data by using a Python or Ruby listener that runs some code when accounts are created. This is a good approach if there's a large amount of data, but has the disadvantage that users need to be online before the seeded data will transfer (and of course the transfer itself will take some time).
There are subtleties with these approaches. With the first approach, using the welcomeNote example, if your user deletes the welcomeNote and subsequently reinstalls your app in the future, the welcomeNote will get resurrected (but never duplicated) because it's being created locally. This is often acceptable. With the second approach, the welcomeNote would be seeded once and only once, so it will never get resurrected even if your app is reinstalled.

How to check lock status and unlock if necessary for Database on Blackberry?

Since I've started developing my Blackberry app, the biggest problems I've encountered all had to do with SQLite Databases.
Right now I'm putting my app through a stress test, and when problems pop up I address them by printing out statuses to the console and taking care of things line by line. Right now (after mashing buttons on my app) I received a "Database is locked" error and I'm not sure what to do.
It seems that once the database is locked it's locked for good until it is unlocked........ my question is how can I unlock it?? First of all, how can I check to see if it's locked??
I'm sure our users won't be mashing buttons like I did, but you never know. I want to account for every possible scenario.
Thanks
EDIT: This is what happens in my application..... When I launch it starts a thread, this thread performs a cleanup on one of my tables based on how old certain pieces of data are (uses DELETE). The thread then continues to get a USER object from my DB (read only), it then uses this USER object as a parameter to call a web service. The data retrieved from the web service is INSERTED into my database. (It's a little more complex than that as a few read/write operations are performed at this time. After that, the thread fires a callback method to update my UI.
This all works fine. I can exit the app WHILE the thread is running and relaunch and a flag will prevent it from starting a new instance of the same thread (unless the other one is done of course).
Now my problem: My app's home screen is a list of buttons, when the user clicks one of these buttons another, more detailed list is loaded (this requires a READ ONLY call to the database). When I launch the app (firing the web service calling thread) and then click a button on the main screen right away, the table gets locked. (Not always, sometimes it takes 4 or 5 tries, sometimes more, sometimes less). But if I keep doing this it WILL eventually lock making it impossible to make any calls to my DB, hence no more UI (which depends on the DB).
The DB call that populates the UI on the second screen is READ ONLY, can't I have as many of these as I need?? What causes the DB to lock?? What's the difference between a DB lock and File System error (12)??
I seemed to have fixed the problem. I was under the impression that if a read/write connection was open then a read-only connection could be created safely.
This doesn't seem to be the case. If I have a read/write connection open then no other connections can open until that one is finished.
I basically created one read/write connection, set a flag to identify it as open, and during my read connection use the same Database object if the flag is open, or create a read only if it's closed.
So far so good.
Sqlite does not support concurrent modification. In practice on BlackBerry, this means you can only open the database from one part of the code at a time. To maintain this one-at-a-time access, you need to close the database when you are done with it, as #AnkitRox points out.
However you also need to guard against concurrent access. Even if your code properly closes the database, it is possible for two different threads to access the database. In that case, you will need one to wait. In Java-ME this is typically accomplished through the 'synchronized' keyword, and using the same lock object for all database access.
Check properly that, you are opening and closing database before and after execution of query respectively.
Because if Database is going to open without closing it properly, then it gives errors.

Resources