Performances DOs and DONTs

Hello CIQ team,

When looking to avoid common CIQ development pitfalls and optimize apps for best user experience, one has to go through many different resources: the programmers guide, this forum, video presentations scattered here and there, etc.

Personally, I would think a dedicated chapter in the programmers guide would be ideal. But maybe we can start in a dedicated forum post (this one) and eventually gather all the information in one place ?

Examples given (as far as I understood; corrections welcome):

Ui.View.findDrawableById():
vs Speed/Battery: this can become a costy operation if a layout has many drawables (and all drawables IDs must be string-compared in turn); this can conventienly be cached after View.setLayout() is called
vs Memory: layout drawables are loaded when View.setLayout() is called; caching only creates another reference to the already in-memory object and has minimal memory impact

Ui.loadResource():
vs Speed/Battery: this is a costy operation, especially for large resources like bitmaps or fonts; the result really ought to be cached and "freed" respectively in View.onShow() and View.onHide()
vs Memory: runtime memory will be allocated when Ui.loadResource() is called and "freed" (garbage-collected) once all referring variables are set to null; strings behavior is unclear, especially given the changes introduced in CIQ 2.4 [to be further clarified]
NB: This should be used even for XML-defined strings; using Rez.Strings.XYZ directly in Lang.format() constructs leads to integer numbers being displayed rather than the string themselves [bug?]

Custom fonts:
vs Memory: custom fonts uses as much runtime memory as the corresponding *.fnt/*.png files; one must: 1. limit the quantity of glyphs to those really needed (you might want to anticipate translations and yet-unused accented characters, though); 2. favor bitmap (2-color) glyphs to aliased ones (with much larger *.png file)
vs Speed/Battery: see Ui.loadResource() above

XML layout vs "hardcoded" Graphics.Dc.draw*():
vs Memory: XML layout objects really do take their toll on runtime memory; when memory becomes an issue, using Graphics.Dc.draw*() calls in ad-hoc annotated functions are the way to do, as the cost of convenience
vs Speed/Battery: see Ui.View.findDrawableById() above

Prefer if/else over switch/case:
vs Memory: strange as it sounds, switch/case stanza uses significantly more memory as the if/else equivalent [bug?]

Multi-dimensional arrays():
vs Memory and Speed/Battery: MonkeyC arrays are objects which creation and memory-overhead cost must not be overlooked. In particular, when dealing with multidimensional arrays - which are 1-dimensional arrays of 1-dimensional arrays of ... - one is better server by keeping the first dimension lower than the second dimension than ...; e.g. given a two-dimensional {n} x {N} where {n} << {N}, better have an array made of {n} arrays of {N} elements than the opposite

Avoid nested menus:
vs Memory: nested menus may provide an elegant way of organizing settings and the like but, as each sub-menu is open thanks to Ui.pushView(), the parent menu(s) is(are) kept on the views stack along all the (memory) resources they use

XML resources IDs:
vs Memory: XML resources IDs are strings; in order to retrieve some of those resources at runtime (layout drawables, application properties), one uses those string IDs in the source code, resulting into corresponding static string objects defined in runtime memory; if saving a few hundered bytes becomes a necessity, one could look into using "integer" (string) IDs in the XML resources and matching constants or enums in the source code.
vs Speed/Battery: string comparisons are not the least costy operation; the shorter the strings the better
PS: I understand such optimization may not be applied to properties, which must be identifiable unequivocally across different builds of the application


Other DOs/DONTs ?

Best,

Cédric

EDITED: Thanks Travis and Jim for your responses; original post edited accordingly
  • yeah. the popping of the menu item from view is good. Saves memory otherwise it my app will "out of memory" error pops up and you get a crash. The downside of popping nested menus is that the user can't "go back" for me, it meant, popping the user back to the Watchface and out of the Menu system each time. It's frustrating but that's must be done to save memory. 

    I even thought of creating a companion app on the phone to transfer app settings to bypass these but I was informed that Garmin doesn't support these pass throughs.

    The other thing which I've thought about is just creating the Companion app (or use a web-app) to parse the settings into a LONG string and then have users paste it into the app-settings page. The properties.xml is also a huge memory user

  • yeah. the popping of the menu item from view is good. Saves memory otherwise it my app will "out of memory" error pops up and you get a crash. The downside of popping nested menus is that the user can't "go back" for me, it meant, popping the user back to the Watchface and out of the Menu system each time. It's frustrating but that's must be done to save memory. 

    If you use switchToView() (on CIQ 3.1 or later devices) and maintain your own stack, you can implement nested menus in a way that behaves just like pushView()/popView(). (Sorry if that was obvious.)

    Even if you can't use switchToView(), you might be able to implement something where you have a placeholder base menu view (with no functionality or drawing implementation) whose only purpose to is to immediately push the current actual menu view. When the user navigates the nested menu system, you can pop the current view and have the base menu view push the next actual menu view.

    The cosmetics of this might not be perfect (the user might see slight lag in transitions, even if you use SLIDE_IMMEDIATE to going to the base view), but it might not be horrible.

    The other thing which I've thought about is just creating the Companion app (or use a web-app) to parse the settings into a LONG string and then have users paste it into the app-settings page. The properties.xml is also a huge memory user

    I think this has been done for a couple of "DIY data field' apps in the store, as well as one "DIY watchface".

    I've thought about doing that for Run Power, as the settings (including settings strings) consumes a ton of memory, especially on 16 KB data field devices (e.g. the ever popular FR235).

    I kinda abandoned the idea when I decided the UX wouldn't be great. (Plus there's the usual uncertainty over how many chars are allowed in a single setting string for Garmin Express vs GCM Android/iOS vs Connect IQ Android/iOS).

    I am *really* disappointed with how inefficient settings are (e.g. with the use of dictionaries), especially the part where setting strings take up app memory (if you call loadResource() at least once), even if the app never uses any settings strings (which would've been the case 99% of the time, especially before on-device settings were created).

    I also don't think the UX of the settings is great (*) and they are very basic, feature-wise (no way to hide one group of settings based the value(s) of other setting(s), for example)

    (*) e.g. Long labels or label with new lines are handled differently on different apps:

    - Garmin Express renders newlines, so you can have long descriptions, lists, etc., to describe a setting

    - Garmin Connect iOS doesn't render newlines, but it does show long descriptions (so you can kind of simulate newlines by adding lots non-breaking whitespace

    - Garmin Connect Android used to just cut off long lines (I think)

    After all this time, there's still no way to reliably put long / structured text in the app settings. There's a really popular app with 400K+ downloads which has a setting whose label is supposed to be displayed with multiple lines/paragraphs. The label looks okay in Garmin Express. In Garmin Connect iOS, all the lines are displayed on a single line/paragraph that wraps, and it looks terrible (basically unreadable).

    The store has a similar issue where there's basically no formatting allowed in the description (at least multiple paragraphs are allowed). If you want to indent text (e.g. for nested bulleted lists), you have to use a unicode non-breaking space (which is a pain, as Chrome changes these non-breaking spaces to regular spaces if you copy and paste). If you want nice horizontal lines, you have to use unicode box drawing chars.

    A long time ago I heard that some CIQ folks wanted to support Markdown for the CIQ store descriptions. Too bad that never happened. It would've been nice to have Markdown in app settings labels too. (And to be able to put text in the settings which aren't even labels.)

  • The other thing which I've thought about is just creating the Companion app (or use a web-app) to parse the settings into a LONG string and then have users paste it into the app-settings page. The properties.xml is also a huge memory user

    I've thought about doing that for Run Power, as the settings (including settings strings) consumes a ton of memory, especially on 16 KB data field devices (e.g. the ever popular FR235).

    I kinda abandoned the idea when I decided the UX wouldn't be great. (Plus there's the usual uncertainty over how many chars are allowed in a single setting string for Garmin Express vs GCM Android/iOS vs Connect IQ Android/iOS).

    Oh and the main reason I abandoned the idea is that *everyone* wouldn't be willing to use the version of the app which requires a companion app/web-app to enter settings. I actually implemented a similar feature in another app of mine -- you can paste in encoded settings from a web app to save run-time memory. At one point, in an attempt to save even more memory, I removed the ability to enter settings normally, and ppl complained, so I had to put it back.

    So with Run Power, it was clear that I would have to maintain two versions of the app: one with normal settings and one with webapp settings, and tbh it wasn't something I was interested in doing.

    TL;DR I wish every Garmin watch had a ton of available memory.

  • Yes, you cannot delete the menu items unless you're really navigating away from the menu.

    The menu object weak reference needs to be passed to submenus to successfully accomplish this. Just navigating back is not enough to free this memory since it's referenced by the "main view" delegate, so the menu objects will be kept alive while that view is alive. 

  • Did anyone ever test, if it's a difference to load one font with 20 icons vs. 20 fonts with 1 icon?

    The second approach allows to granularly load only used icons.. I know some people do split up icon fonts (e.g. day weather icons vs night weather icons) and I do this as well, but still I do keep a few unused icons in memory all the time because the user never needs all available icons at the same time. I wonder if someone ever tested splitting up icons in single fonts and if this does have drawbacks memory wise...

  • Another thing to consider is string loading - 2 things to consider here: it may fail in onPartialUpdate in the future (I'm not sure where I've read this, but I think I remember that this will come in CIQ4?) and it cost's time. So a good idea is to cache them (at least the ones that do not change very often like e.g. the month name or the week date) and reuse the cached strings. This needs a little more memory but saves calculation time in return.

  • Partial Updates

    If you have complex and slow layouts and can't cache everything for those layouts, partial updates are very effective. It's not supported though - this means, whenever something is drawn partially above your watchface you will see artifacts because the system always relys on full screen redraws... I do this in one of my WFs and could nearly half the execution time with this technic and also starting an activity is double as fast with this technic. But it makes only sense imho if your layout is very complex (mine needs to load/unload multiple fonts in each draw because otherwise I would exceed the memory limits)...

    Measure battery impact

    If execution time is less battery impact should be less as well - that's what I measure so far.

  • The only time it's really safe to only update part of the screen is in onPartialUpdate().  Some devices clear the screen prior to onUpdate() as you are expected to redraw the full screen.  With some, system "toasts" can obscure part of the screen, expecting that you do a full draw.  These are both things you don't see in the sim, but only on real devices, and they vary by device.

    Just as a note, starting with 3.2.5, you are not allowed to do things like load resources in onPartialUpdate - along with pretty much anything that can result in hitting the file system.

  • Ok, this kinda developed into a rant, sorry. But sometimes it gets frustrating when you want to support the platform and feel no one is listening.

    :-p you mean no one's reading 

  • Yeah its in the metaphorical sense, of course. I hope they are reading. But at the same time I also understand that no one at an official level would feel comfortable to address such "emotionally loaded" outbursts.

    I just wonder how does Garmin CiQ dev team(s) prioritise their work, I see very important (for us devs) feature and bug tickets living for months, years even... with no transparency regarding their priorities or how they plan to make developer's life's any better. Very little communication and very little involvement with the community.

    I want to make better Connect IQ software, but Garmin doesn't make it easy.

    I won't start listing things now as I think we all know about the pain-points of developing CiQ software. The thing is, there seems to be no movement towards fixing/mitigating them.