Garmin devices come in different shape and sizes, have different memory constraints, and have different levels of Connect IQ compatibility. To be able to maintain different Connect IQ devices you can use the has functionality to test whether a certain feature is present on the device, but applying this technique too much makes the code unmanageable. You could also set up a Connect IQ project per device and copy/paste your code across your projects, but this is very labor intensive to keep your code in sync and is prone to mistakes.
Using one code base to handle all Garmin devices compatible with Connect IQ is easier than you think. All you have to do is combine three core features of the Connect IQ framework: inheritance, resource overrides, and file excludes. Let’s see how these features help you to solve the issues that held us back.
Screen Geometry
One way to solve different size/shape issues is to use the layout system. This works perfect for watch faces as you have a lot of memory available there. In data fields, the memory budget is usually tight and as the layout system has a higher memory requirement, I got used to opting for using direct drawing on the dc object instead. I want to have the same benefits as the layout system though, as I might want a different layout on each device.
So how do we do this?
- Define a base class CommonLogicView where we handle all logic that we have to do on each device.
- Define a class called DeviceView for each device that we want to support. This DeviceView inherits from the CommonLogicViewand implements the layout specifics for each device by drawing to the dc
- For each device create a build resource exclude where you exclude all other device class implementations. Let’s say that you’re trying to build for the original vivoactive and the Forerunner 735XT. These two products have different screen geometry and support different versions of Connect IQ. The picture below shows how the project will look like after this has been set up:
- Post in the comments section below!
The resources/fr735xt file excludes are defined in sources.xml. We simply specify which code files we do NOT want in the build for this device. We apply a similar tactic for each device we implement:
1 2 3 |
<build> <exclude file="../source/Vivoactive.mc" /> </build> |
The CommonLogicView is the base view. Here we handle all code that we can use in all the device implementations. It’s a pretty easy class, the main entry point into this class is the onUpdate function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class CommonLogicView extends Ui.WatchFace { ... // this is called whenever the screen needs to be updated function onUpdate(dc) { // Common update of the screen: Clear the screen with the backgroundcolor. clearScreen(dc); // We can also do common calculations here, in this case we'll just get the current time determineClockTime(); } // clears the screen and stores the width & height in global variables function clearScreen(dc) { ... } function determineClockTime() { ... } } |
The DeviceView is where we implement the specifics of the device, so for the Forerunner 735XT we could have something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class DeviceView extends CommonLogicView { // inherits from CommonLogic function initialize() { CommonLogicView.initialize(); // important to call the initialize of your base class, even when you have nothing to add… } function onUpdate(dc) { CommonLogicView.onUpdate(dc); dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_TRANSPARENT); dc.drawText(width / 2, height / 2, Gfx.FONT_NUMBER_THAI_HOT, timeString, Gfx.TEXT_JUSTIFY_CENTER | Gfx.TEXT_JUSTIFY_VCENTER); dc.setColor(Gfx.COLOR_BLUE, Gfx.COLOR_TRANSPARENT); dc.drawText(width / 2, 140, Gfx.FONT_SMALL, "I'm Peter's favourite!", Gfx.TEXT_JUSTIFY_CENTER); } } |
For the vivoactive we have a similar file, but then implementing the specifics of the vivoactive:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class DeviceView extends CommonLogicView { function initialize() { CommonLogicView.initialize(); // important to call the initialize of your base class, even when you have nothing to add… } function onUpdate(dc) { // call the parent function in order to execute the logic of the parent CommonLogicView.onUpdate(dc); dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_TRANSPARENT); dc.drawText(width / 2, height / 2, Gfx.FONT_NUMBER_THAI_HOT, timeString, Gfx.TEXT_JUSTIFY_CENTER | Gfx.TEXT_JUSTIFY_VCENTER); dc.setColor(Gfx.COLOR_PINK, Gfx.COLOR_TRANSPARENT); dc.drawText(width / 2, 110, Gfx.FONT_MEDIUM, "I'm a cool watch!", Gfx.TEXT_JUSTIFY_CENTER); } } |
When we run the code this results in the following in the simulator:
Full Source Code
Memory Constraints
The Forerunner 235 and Forerunner 735XT are both semi-round, but they have different memory limitations. For instance, in data field mode, the FR235 allows for 16K of memory, while the FR735XT has 26K of available memory to play with. That means we can pack 10K more goodies in the 735XT data field!To do this we apply the same technique of combining inheritance, resource overrides and file excludes, just adding one layer of extra inheritance does the trick:
In my Peter’s (Race) Pacer app I’m taking this technique to the extreme and have 4 layers of inheritance defined: CommonLogic, LayoutLowMemory, CommonHighMemoryLogic, and LayoutHighMemory.
All layers inherit from each other so that I don’t have to write a single letter of duplicate code. This allows me to support all combinations of Connect IQ 1.x and 2.x devices from a single code base, while getting the max out of the available memory for each device!
Connect IQ Version Compatibility
We are treated with more and more features being added to the Connect IQ framework. Older devices can not be upgraded to Connect IQ 2 because they don’t have enough processing power and/or memory available. Luckily we can apply the same technique of combining inheritance and file excludes to be able to use new Connect IQ 2 features in our projects while simultaneously also retaining support for users that have Connect IQ 1 devices. Cool, eh?Let’s demonstrate this with a sample project. I’m going to extend the watch face with the ability to pull “to do” lists from a web-service via a background process, while at the same time we continue to support Connect IQ 1 devices. Of course the functionality on the Connect IQ devices will remain the same as before the changes as there is no alternative to background services in Connect IQ 1.x.
We open our project from before and add the Connect IQ abstraction layer, this is the one I’ve defined for Connect IQ 1:
1 2 3 4 5 6 7 8 |
class CiqView extends CommonLogicView { hidden var message; // I could add this variable to the CommonLogicView class, this is to show that you can define class instance variables at any level in the inheritance tree… function initialize() { CommonLogicView.initialize(); message = "I'm a CIQ1 device!"; } } |
1 2 3 4 5 6 7 8 9 10 11 12 |
class CiqView extends CommonLogicView { hidden var message; function initialize() { CommonLogicView.initialize(); } function onUpdate(dc) { CommonLogicView.onUpdate(dc); message = App.getApp().backgroundMessage; } } |
We’ve also duplicated the application layer level. The Connect IQ 1 version is pretty straightforward (you can have a peek at the source on github). The Connect IQ 2 version is slightly more complex as we’ll implement a background service to fetch from a webservice here (trimmed for readability):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
(:background) class PetersWatchfaceApp extends App.AppBase { var backgroundMessage = "What should I do?"; function initialize() { AppBase.initialize(); } function getInitialView() { // register for temporal events, we don't need to check whether this is supported, we know we have a Connect IQ 2 device here! Background.registerForTemporalEvent(new Time.Duration(5 * 60)); return [ new DeviceView() ]; } function onBackgroundData(data) { // store the data from the background event in a class level variable backgroundMessage = data; // we received a new todo item, invoke a request to update the screen Ui.requestUpdate(); } function getServiceDelegate(){ return [new BackgroundServiceDelegate()]; } } |
The service delegate is the main entry point for the background service, inside we’ll fetch a random to do item from the web-service.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
(:background) class BackgroundServiceDelegate extends System.ServiceDelegate { function initialize() { System.ServiceDelegate.initialize(); } // this is called every time the periodic event is triggered by the system. function onTemporalEvent() { Comm.makeWebRequest( "https://jsonplaceholder.typicode.com/todos/" + (Math.rand() % 100 + 1), // get a random number between 1 and 100 {}, { "Content-Type" => Comm.REQUEST_CONTENT_TYPE_URL_ENCODED }, method(:onReceive) ); } // receive the data from the web request function onReceive(responseCode, data) { if (responseCode == 200) { Background.exit(data["title"]); // get the title part of the todo list… } else { Background.exit("Error " + responseCode); } } } |
Full source code
Hope you’ll like this technique!
Happy coding!
Hi Peter, first of all thx for all the tutorials! I’m a completely newby… did some coding for the Suunto Ambits, but this is a new level. So was glad to find your site and some code examples on Github. After implementing your commonLogic technic, i couldn’t get rid off an build error :
*Attempting to redefine object DeviceView*
it works, the build is not failing, and i got this error only for the second deviceview i created. Any hints on this?
can use my email!
thx in advance!
hi carsten,
The failure you see is due to the introduction of the “jungle” concept (even when you use only resource overrides). To fix the error pick a specific device for the autobuild, eg if you pick the fr735xt then add this to your monkey.jungle file:
# fix autobuild to a specific device
base.sourcePath=$(fr735xt.sourcePath)
In fact I would advice to use jungles throughout as it’s a nicer way to handle things, I’ve specified on how to do all that here: https://developer.garmin.com/index.php/blog/post/pro-tip-tackling-connect-iq-versions-screen-shapes-and-memory-limitations
Happy coding 😉
Cheers,
Peter.
got it ! thx a lot
Great article! Thanks Peter for sharing!