How to avoid heavy API calls each second in a datafield?

I'm developing a complex datafield that's pulling Activity.Info but also Weather under compute. Under onUpdate the dc is drawn every second (using direct draws only).

1. What's a clean and preferred way to avoid overloading on Weather (or other heavy calls) when the datafield naturally refreshes every second?

Direction & speed, and other navigational data, are also called every second from Activity.Info but at least these don't require an external API call, whereas Weather calls need to pull this from the mobile phone (cached and updated every 20 mins I read) - still it seems taxing to make such heavy API call each second.

2. Am I correct in thinking that direction and speed pulled every second is reasonable for a datafield, or should some pause be introduced here as well, and if so what's the cleanest approach?

New to this and getting results is relatively easy, but knowing the design and approach is appropriate is something very different!

  • Just a small addition regarding the timer. Weather data is unrelated to the activity data so sometimes you may want to see it updated even when the activity has not yet started or after it has finished. For this reason, a system timer may be used instead of the elapsedTime, something like this:

        private static const INTEGER_MIN = -2147483648;
        private static const WEATHER_UPDATE_INTERVAL = 600000;
    
        private var _weatherLastUpdated as Number;
    
        public function initialize() {
            // System.getTimer() may return negative values, so initialize _weatherLastUpdated as min 32-bit integer
            _weatherLastUpdated = INTEGER_MIN;
        }
    
        public function compute(info as Activity.Info) as Void {
            var now = System.getTimer();
    
            // handle overflow of system timer which occurs ~25 days after the device reboot and every ~50 days thereafter
            if (now < 0 && _weatherLastUpdated >= 0) {
                _weatherLastUpdated = INTEGER_MIN;
            }
    
            if (now > _weatherLastUpdated + WEATHER_UPDATE_INTERVAL) {
                var weather = Weather.getCurrentConditions();
                
                // do some stuff with weather
    
                _weatherLastUpdated = now;
            }
        }

  • Just a small addition regarding the timer. Weather data is unrelated to the activity data so sometimes you may want to see it updated even when the activity has not yet started or after it has finished. For this reason, a system timer may be used instead of the elapsedTime, something like this:

    You may to avoid using a timer for this type of thing in general, as there's only 3 timers max and as your code indicates, there are a few edge cases to handle. Also, timers aren't allowed in data fields, so the idea is actually a non-starter (unless the doc is wrong and/or something changed recently). If you were implementing an app type that does support Timers (such as a device/watch app), you might already be using one timer to regularly update the screen, and it wouldn't be worth using a second timer to schedule an event like this if you can avoid it (imo).

    EDIT, as SpitRider pointed out, he's referring to System.getTimer() (the system uptime), not Timer.Timer. System.getTimer() can ofc be used in a data field, but the only issue is the slight complexity associated associated with integer overflow and negative values as mentioned in the code comments.

    A simpler solution would be to increment a counter by 1 on every call to compute (which is called regardless of whether the timer is paused/stopped, the timer is not yet started, or the activity is finished *). (* I don't have a watch where activities stay "alive" after you save them, so I'm just assuming that compute would continue to be called after the end of an activity, but I'm pretty sure about the other 2 cases.)

    Since compute is called *roughly* every second and -- as discussed at length -- precision isn't super important here, just check whether your counter is equal to the timeout (e.g. 600 seconds) after incrementing it. If so, perform the action and reset the counter to 0.

    Or start the counter at the timeout value and decrement on each call.

  • The CIQ naming is quite confusive :)

    This is not a timer from the Timer module (which is indeed unavailable in datafields), this is just a time since the last device boot from the System module which counts millis independently of the activity timer or the internal datafield timer used to invoke the compute() and onUpdate() methods.

    But agree that using the compute() method itself looks even easier for this particular case.

  • The CIQ naming is quite confusive :)

    This is not a timer from the Timer module (which is indeed unavailable in datafields), this is just a time since the last device boot from the System module which counts millis independently of the activity timer or the internal datafield timer used to invoke the compute() and onUpdate() methods.

    But agree that using the compute() method itself looks even easier for this particular case.

    Haha sorry you're absolutely right. I focused on the phrase "a system timer" without reading the code properly, even though it should have been obvious with the comments about workarounds for integer overflow/wraparound. I really wish they had called it uptime or system time instead. So many things in CIQ/Monkey C are designed/named strangely imo.

    As a matter fact I have used the system time(r) for measuring more precise intervals of time myself (and ofc I've had to worry about overflow as well), so I feel twice as dumb here.

    Thanks for the suggestion and clarification! I'll edit my comment.

    That's def my bad for:

    - not reading the comment carefully

    - not giving you the benefit of the doubt (ironically when I first saw your comment my instant reaction was "great idea", but then I took a second glance and thought "wait". Next time I'll check 3 times haha.)