Object-oriented watchface

This is just a note on an approach I settled on that I think works pretty well. I am sure many people have come up with the same idea (or better), but I just thought I would share it here. Happy to hear any feedback or suggestions on improvement.

In short, I decided to take an object-oriented approach to implementing datafields on my watchfaces. I created a parent class, "Complication" that stores anything on a watchface other than the watch hands (using the traditional watchmaker terminology for such things). Then, a watchface consists of an array of Complications, and the onUpdate method is particularly elegant in my opinion.

function onUpdate(dc as Dc) as Void {
    
    // Draw the complications
    
    // _coords = An array storing x,y coordinates for each complication
    // _complications = An array of objects of class Complication
    // _inLowPower = Variable indicating current low-power status
   
    for(var i = 0; i < _complications.size(); i++) {
        var x = _coords[i][0];
        var y = _coords[i][1];
        _complications[i].draw(dc, x, y, _inLowPower);
    }
    
    // Draw watch hands (for analog watchface)
    // ...
    // Add mask overlay for burn-in protection (if needed)
    // ...
}

The prototype for the Complication class is as follows:

class Complication {

    private var _refresh_interval = null;
    private var _last_refresh = null;

    function initialize(refreshInterval) {
        _refresh_interval = refreshInterval;
    }

    function refreshInterval() {
        return _refresh_interval;
    }

    function getLastRefresh() {
        return _last_refresh;
    }

    function setLastRefresh(refresh_time) {
        _last_refresh = refresh_time;
    }

    function refresh() {
    }

    function draw(dc as Graphics.Dc, x as Number, y as Number, lowPower as Boolean) {
    }
}

Other than the draw() method, it also has methods to get and set the last time the complication was refreshed. This is useful because most complications don't need their data updated at 1Hz, and allowing separate data refresh rates for each complication can save a huge amount of time and power. Each time a complication object is created, you specify its refresh interval,

_complications[0] = new Complication(60) // Create a complication that only needs to be updated once per minute

I have a separate function that steps through the array of complications, queries the time since its last refresh, and calls its refresh() method only if needed.

In practice, each type of complication on my watchface is a child class of Complication. For example, here is the complete code for a HeartRate complication:

class HeartRate extends Complication {

    var _img_hr;    // Stores a bitmap image
    var _hr_string; // Stores the text HR string to be displayed

    function initialize(refreshInterval) {
        Complication.initialize(refreshInterval);
        _img_hr = WatchUi.loadResource(Rez.Drawables.HR);
    }

    function refresh() {
        var heartRate = Activity.getActivityInfo().currentHeartRate; //get the latest HR if available
        if (heartRate == null) {
            if (ActivityMonitor has :getHeartRateHistory) {
                var hrHist =  ActivityMonitor.getHeartRateHistory(1, true);
                heartRate = hrHist.next().heartRate;
            }
        }
        if (heartRate == null || heartRate == ActivityMonitor.INVALID_HR_SAMPLE) {
            heartRate = "---";
        }
        _hr_string = heartRate;
    }

    function draw(dc as Graphics.Dc, x, y, lowPower as Boolean) {
        var img_width = _img_hr.getWidth();
        var img_height = _img_hr.getHeight();
        dc.drawBitmap(x - 0.5 * img_width, y - img_height, _img_hr);

        dc.setColor(dc.COLOR_WHITE, dc.COLOR_TRANSPARENT);
        dc.drawText(
            x, y,
            Graphics.FONT_TINY,
            _hr_string,
            Graphics.TEXT_JUSTIFY_CENTER);
    }
}

It's nearly trivial to add new complications without rewriting huge chunks of existing code, because any new complications conform to the class template and use its existing methods.

Adopting this class simplified my code so much that I decided I needed to make things more complicated (no pun intended). So now I also use triple-buffering so that complications are refreshed and redrawn to an offscreen buffer asyncrhonously from the onUpdate() method. I will save that for another post!