DataField - Best way to draw

I'm new to the Garmin SDK and I'm working on a custom Connect IQ datafield (a complex datafield, not simple)

Whats the best way of drawing the actual data field ? Should it be in onUpdate (dc) ? I see many calls to View.findDrawableById, but no calls to the drawing methods of the dc which is passed as a parameter to onUpdate. Which ones should I use ?

Basically I want to create a new Data Screen for my Epix Pro Gen 2 that shows multiple fields like the current time, timer, and a couple of my own custom calculations.

And the data screen would be a 1-field layout using a ConnectIQ data field. Something Similar to the Data Field "Walker" available in the Connect IQ store.

  • You draw in onUpdate. If you use layouts then you should only execute the findDrawableById once in onLayout and save the results and use them. If you don't use layouts (probably faster and smaller code) then you call the draw functions on the cd that you get in the parameter of onUpdate(dc). Walker has the code in github, and there are also other 1-field datafields with code in github.

  • In onUpdate[], if you are using direct drawing methods rather than layouts, you will probably mostly use drawText[] and drawLine[]. For example, drawTextp[ would be used for data field labels and value, while drawLine would be used for field separators. 

    Here’s an example [GRun]:

    [https://apps.garmin.com/en-US/apps/bf56e088-aefe-4fb4-8f16-b2e136b996a9]

    [https://github.com/gcormier9/GRun/tree/master]

    It’s somewhat complex, but also fully featured.

    The approach that’s used here, which is to calculate layout coordinates at runtime, is somewhat memory-intensive [in terms of the amount of code that just calculates layouts], which means that it doesn’t work for really old devices like FR235 [9 years old] which have extremely limited memory. But most devs are probably ok with excluding those devices at this point, even if lots of Garmin users hold on to their ~10-year old devices.

    Personally, for my own fullscreen CIQ data fields, to have the slimmest code possible (so I could support all the old devices) I used a hybrid approach with dc.draw* methods that renders layouts from my own custom-made layout system [with pre-computed coordinates], but it’s super labour-intensive and hacky [bc I didn’t auto-generate the coordinates, but I did the layouts by hand]. I did make these fields around 6 years ago, when the older devices were perhaps a bit more relevant.

    If you confine yourself to fenix 5 / fr935 and newer [CIQ 3+ devices which are 7 years old at this point], you’ll probably be fine with an approach similar to GRun’s.

  • Last time I checked it was the opposite: compiling with layouts adds some code and data, thus most probably it'll end up using more memory than if you'd only used 1 drawText in onUpdate.

  • Last time I checked it was the opposite: compiling with layouts adds some code and data, thus most probably it'll end up using more memory than if you'd only used 1 drawText in onUpdate.

    I was not referring to standard CIQ layouts (in resource xml), but a hybrid approach that renders layouts from my own custom-made layout system [with pre-computed coordinates]. I didn’t go into details, but one of the ways this uses less memory is that all the layout/coordinate data is efficiently encoded into bit-packed arrays of integers. My layout data is stored either in JSON resources (CIQ 2+) or array data in code (CIQ 1).

    I’m also not comparing a whole standard CIQ layout to “1 drawText in onUpdate”, I specifically mentioned grun, which has a ton of code in onUpdate to calculate layout coordinates at runtime. (That’s how it’s able to draw several fields on the screen without precomputing any coordinates.)

    I tried 3 different approaches, with the following results:

    - CIQ layouts [in resource xml]: most memory

    - grun [direct dc.draw* calls with runtime layout computation]: medium amount of memory

    - hybrid system [my own custom pre-computed layout system]: least amount of memory

    Ime precomputing the layout is more efficient (memory-wise), at least when you have your own bespoke layout system, especially for CIQ 2+ devices which have JSON resources so you don’t have to keep the layout data in memory all the time. You can load the layout data in onUpdate(). At other times, when the layout data is not being used, you can use that memory for something else. It’s also helpful for the use case when you have memory spikes at init time [for whatever reason], since you don’t have to have the layout data loaded at that time either.

    But even for CIQ 1 devices, where I have the layout data literally in code [as arrays of ints], it’s still more efficient memory-wise than grun. grun won’t even fit in devices like fr235.

    I‘d guess that precomputed layouts are also more efficient battery wise as opposed to re-computing the layout on every onUpdate(),  but I have no idea if the user would ever notice an impact on real world battery life.

  • The basics are you do all the screen updates in onUpdate, and when onUpdate is called, you need to draw everything.  It's called every second when the DF is visible, but if you switch to a different data screen, it's not called.  If the same DF is used more than once on the same data screen, onUpdate (as well as onLayout) is called for each one.

    compute is called every second, and it doesn't depend on if the DF is visible or if it's included more than once, so anything you calculate should be done in compute and not onUpdate

  • Thanks everyone for all the valuable information !

    So if I understand correctly, I need to completely redraw my datafield from scratch everytime onUpdate() is called ? There’s no way of caching the dc, or only updating the portions that have changed and then “bit blitting” them to the display?

    Initially I used the default code Garmin provides which ends with a call to View.onUpdate() – but it overwrites any drawing operations performed on the dc. So if I use dc to draw should I remove the call to View.onUpdate() ?

    Also, where is View defined ?

    What I'd like to do is load a bitmap with the graphical design of the datafield and use it for the background, and only do this once during initialization, then when onUpdate() is called - only update the values of the fields - without having to reload and redraw the bitmap. Is that even possible ?

  • Can I use both layouts and the dc ? So onLayout() I would load a bitmap with the graphical design of the datafield, then use the dc in onUpdate() to only update the field data ?

  • View.onUpdate Draws what's defined in the layout.  If you want to do direct dc stuff you need to do them after View.onUpdate.   If you aren't using layouts, you don't need to call View.onUpdate

  • Why would you do that? Why not drawing the background at the beginning of onUpdate and then draw above it?

  • Because onUpdate() gets called every second, and since the design doesn't change, it seems wasteful to redraw it every second. ideally I would like to draw the design only once during initialization, and then update only the data field values every second.

    Maybe I am not fully understanding the concept...