Simulator: can't push settings to data field

Windows 10
SDK: CIQ 3.0.7
Complete source code available upon request (*)

This is a problem that I've been wrestling with on multiple data field projects for a while now.

Sometimes when I send settings to a data field in the simulator and re-run the data field (because I don't handle onSettingsChanged), not only do the settings not get applied, but they also get reset to default.

This only happens in the simulator, not IRL.

EDIT: Now I have seen this happen IRL, with the same app (store version). I updated the app with two new booleans, and after I pushed settings, they were reset to default.
https://apps.garmin.com/en-US/apps/7...b-e97269c3cdb9

Sometimes there is a workaround where I can kill the app before sending settings, and it will work. Other times it seems like there is nothing I can do to push settings.

In the latter case I resort to hardcoding test settings in the properties XML, running the app and selecting "Reset App Data".

This is pretty frustrating and annoying, because my apps tend to have lots of settings <_< and I like testing them by changing them in the same way that the user would.

App notes:
- For compatibility across all watches, new and old, I am using Application.getApp().getProperty() to get settings
- I tend to have different settings for different targets (e.g. CIQ1 vs. CIQ2), in order to max out the capabilities of each platform
- I tend not to handle onSettingsChanged() (terrible practice, I know), because of the slight extra hit it might take in code that has to be smart enough to reinitialize certain things at run-time. In my defense, I've seen very popular apps that don't/can't handle onSettingsChanged(), and it's only really good for cosmetic changes anyway. If a metric type is changed in the middle of an activity, it may be literally impossible for the app to display that metric properly, since some metrics need to be calculated from the start.

(*) This has happened to me with so many apps, but I don't have a simple test case, I'm afraid. I do have complete source code for an app ("Lap+") where this has happened. It's source-controlled, so I'll be able to give you a usable copy of this at any time.

I wonder if having the different settings per-target "family" has something to do with it. However, I tried a very small test case and it doesn't to be that.

So maybe it's just having a large number of settings that's the problem.

Recreation procedure:

A)
-
Open "Lap+" data field project in Eclipse. Complete source code available on request.
- Run app for FR935 in the simulator
- Open app settings
- Change "theme color" to dark green. (This is the easiest setting to change where you can see it didn't work.)
- Send settings to simulator
- Run app again.
- The actual theme color will be unchanged.

B)
-
While app is still running, open app settings
- Kill the app (without closing the app settings or the simulator)
- Change the theme color to dark green
- Send settings to simulator
- Run app again
- The actual theme color will be changed to dark green

C)
- Now that the theme color has been changed, repeat A), except try to change the colour to dark blue (or anything but dark red, the default).
- The selected theme color will not be applied. As a matter of fact, when you run the app again, the theme color will be reset to the default (dark red).
  • That is exactly what this code is doing. Instead of taking down the app, we choose to ignore the settings file and use the defaults.


    It isn't the saving of the settings file that is the problem. It is loading the settings that is the issue.


    The settings are loaded immediately if the app is running, otherwise they're loaded on startup. You're suggesting that if we fail to load the settings due to a memory error, we should just leave the data in place and load it on the next startup?


    I understand why this is not desirable, since it might lead to the app crashing in an infinite loop.

    However, might I suggest a slight modification to the algorithm (bolded below):

    e.g.
    If (LoadSettingsFailed)
    {
    // Instead of unconditionally using default setting on load failure
    // why not give it one more try if the app was running?
    // The next try should have a bit more memory available
    If (!AppAlreadyRunningWhenSettingsLoaded)
    {

    UseDefaultSettings();
    }
    ReloadApp();
    }


    The motivation here is that we all agree that it takes more memory to load settings if the app was already running (as opposed to loading settings at startup). Therefore, the app should be given one more opportunity to load a given set of settings, if it was already running at the time and it crashed. If It fails again, then I would be okay with the default settings being applied at that point.

    Alternatively (or additionally), please allow devs to opt out of loading settings at run time.

    The difference in behavior occurs because when the app is running you hold all of the available memory, and at startup you haven't had a chance to horde it all.

    Yep, that's what I figured.

    This would reduce the memory load at the time the settings are loaded.


    Yep, that's also what I figured.


    I have mentioned how helpful it is to have test cases before, right? :-)


    Haha fair enough. Okay, to be completely honest, I said "consistently crash in the sim", but it's actually sort of intermittent. It's almost like a Heisenbug where it sometimes disappears when I intentionally try to recreate it. That's why it took me so long to finally file this bug report even though I've seen this kind of behaviour for a long time. What concerns me the most is the difference in behaviour between the sim and the real devices, although I do understand that differences are inevitable.

    If I had test cases that would cause an app to crash 100% of the time on both the device and sim, then I obviously wouldn't release in that state. If the app is only crashing in the sim but I can't make it crash on the real device, then I'm liable to throw caution to the wind and release like that. Maybe it's not the smartest move, but I have to differentiate my apps somehow, and the way I do that is to cram as many features as possible into some of them.

    I concur and i think i also suggested this in the past.

    If the app isn't going to do anything with the new settings it makes no sense trying to have them loaded.

    the preferably way would be that the connect iq framework can detect that reloading settings isn't necessary, but in case this isn't possible i wouldn't mind flipping a switch in the manifest or jungle file to specify that I don't support setting changes in this app.


    Thanks Peter. I was worried I was the only one. I figure that a switch in the manifest would be the perfect way to handle this.

    I think this is just a case where we all sort of understand why it works the way it does, but some of us wish it was slightly different.
  • Just a note that I was experiencing something similar to this recently on a watchface I've been testing as a beta app on the store. (I never managed to reproduce it on the simulator).

    When I sent new settings from Garmin Connect Mobile (Android) while the watchface was currently active, then sometimes they would not get received. Instead all settings would be reset to default (and I think the watchface was restarted?) Occasionally the watchface would just stop being drawn and parts of the default watchface would draw over the top (e.g. just a heart rate value updating every second) - then at the top of the minute the whole default watchface would be drawn.

    Peak memory usage for my watchface was only ever reaching 88kB (out of the 92kB) on the simulator (only reaching that when sending settings), so I assumed it was to do with memory fragmentation and being unable to load the new settings into memory.

    The problem went away when I reduced my total number of properties, reduced my peak memory usage and changed my dynamic resource loading & releasing (fonts) to be done at a different time to try and avoid fragmentation.

    I've no idea how the memory allocations for CIQ are handled though - is memory fragmentation something that can happen? Does a font resource which uses 4k need a contiguous block of 4k memory?
  • Yes, memory fragmentation can happen, and for things like re-loading the properties (or a font), it's not just available memory, it needs contiguous.

    That's why doing something like array.add() can cause problems with large arrays. - it needs the original array, and then get enough contiguous memory for a size+1 array, and copy over the old array before freeing that chunk of memory.

    I wonder how many devs use properties for things that really should be in the object store. That would be a case where the memory copy and the disk copy of the .set could easily get out of sync if the memory copy doesn't get re-loaded when settings are changed. Same if the app itself changes a property related to a setting. I know that happens too.