• New Categories in Connect IQ Store

    As announced at GDVC we have updated the categories on the homepage of the Connect IQ store. These new categories provide new opportunities to get your app on the homepage.

    Use the following to map the developer category to the front page category:

    Homepage Category

    Intent

    App Type

    Developer Category

    Music

    All audio apps

    Audio Content Provider

    Music, Podcast, Practice, or Workout

    Style and Substance

    Watch faces focused on style

    Watch Face

    Stylish

    Data Fields for Runners

    Data fields specifically for runners

    Data Field

    Running

    Navigate Your World

    Apps involving location or direction

    Device App, Widget

    Navigation, Geocache

    Simply Amazing

    Watch faces focused on simplicity

    Watch Face

    Simple

    Data Fields for Cyclists

    Cycling focused data fields

    Data Field

    Cycling

    Weather at a Glance

    Weather apps

    Device App, Widget

    Weather

    Geek Chic

    Watch faces with geek or retro appeal

    Watch Face

    Geek, Retro

    Sensors and Displays

    Data fields that communicate with wireless external sensors or displays

    Data Field

    Sensor, Secondary Display

    Animal Adventures

    Watch faces with animals

    Watch Face

    Animal

    Best Estimates

    Data fields that compute new metrics

    Data Field

    Estimate, Counter, Environmental, Goal, Consumption

    Indoor Workout Data Fields

    Data fields for the gym

    Data Field

    Gym

    Power Tools

    Apps and widgets about helping the user be productive

    Device App, Widget

    Tools, Social, Communication

    Around the World

    Watch faces that focus on other parts of the world

    Watch Face

    Around the World

    Workout Dashboards

    Data fields that are dashboards of multiple metrics

    Data Field

    Dashboards

    Health and Wellness

    Apps focused on the health and wellness of the user

    Device App, Widget

    Medical, Walking, Wellness, Health and Fitness

    Data Rich Watch Faces

    Watch faces focused on metrics

    Watch Face

    Utility

    Performance Metrics

    Data fields focused on helping an athlete be their best

    Data Field

    Performance

    Having Fun

    Apps and widgets about escaping productivity

    Device App, Widget

    Games, Entertainment, Food and Drink, Sports, Celestial

    Analog Watch Faces

    Watch faces with analog hands

    Watch Face

    Analog

    Training Metrics

    Data fields focused on helping the user train for an event

    Data Field

    Training,  Swimming

    Active Lifestyle Apps

    Apps and widgets about the user's active lifestyle

    Device App, Widget

    Running,  Swimming, Cycling, Strength Training, Golf, Hiking

    Fun Watch Faces

    Watch faces that are about bringing joy

    Watch Face

    Fun, Cartoon, Fantasy, Family

    Out to Sea

    Watch faces, data fields, widgets, and apps about being on the water

    Device App, Widget, Watch Face, Data Field

    Marine

    Workout Tools

    Data fields that provide tools for your workout

    Data Field

    Tools

    Digital Watch Faces

    Non-analog watch faces that do not fit into other categories

    Watch Face

    Digital

    Race Day

    Data fields focused on the day of the event

    Data Field

    Competition

    Natural Beauty

    Watch faces that bring the user closer to nature

    Watch Face

    Nature

    Home and Away

    Apps focused on integration with the home or on travel

    Device App, Widget

    Travel, Home Automation

    Better Living Through Apps

    Catch all category for widgets and apps that don’t fall other categories

    Device App, Widget

    Lifestyle, Business, Finance, Education, Other, Beliefs

    Useful Workout Metrics

    Catch all category for data fields that don’t fall into other categories

    Data Field

    Guidance

    • Apr 29, 2021
  • Venu 2 Series Now Available in Connect IQ SDK Manager

    Today Garmin announced the Venu® 2 series. This family of GPS smartwatches, available in two sizes, has advanced health monitoring and fitness features to help you better understand what’s going on inside your body.

    With the advanced features on the Venu® 2, you can take your activities to the next level. Switch up your activities with animated workouts and more than 25 built-in indoor and GPS sports apps. You can feed your soul with your favorite music, stored right on your wrist for phone-free listening when paired with wireless headphones (not included). Thanks to a battery that features rapid recharging and can last up to 11 days, you’re sure to get an uninterrupted picture of your health.

    The Venu® 2/2s is our first Connect IQ product using API level 4.0.0 and requires the Connect IQ 4.0.0 SDK. Use the Connect IQ SDK Manager to update your device library and add Venu® 2 support to your apps.

    • Apr 22, 2021
  • Welcome to Connect IQ System 4

    Today we are launching Connect IQ System 4, the most advanced Connect IQ to date. Connect IQ System 4 brings powerful new graphics tools and a new type system for the Monkey C language. It also introduces Super Apps.

    What is System 4?

    Connect IQ now uses two versions: the API level and the system number. The API level is a three-digit (major.minor.micro) version that dictates the potential APIs that a device supports. Not every device supports every API, but if a product is at or below an API level, it is guaranteed to not support an API. The system number is a single-digit number that is associated with a minimum API level. The system number communicates the set of devices that meet the minimum API level.

    SYSTEM 1

    SYSTEM 2

    SYSTEM 3

    SYSTEM 4

    API Level <= 1.5.0

    API Level <= 2.4.0

    API Level <= 3.1.0

    API Level >= 3.2.0

    D2Tm Bravo

    Approach® S60

    Approach® S62

    DescentTm Mk2/i/s

    D2Tm Bravo Titanium

    Forerunner® 735xt

    D2Tm Air

    Edge® 530

    epixTm

    vívoactive® HR

    D2Tm Charlie

    Edge® 830

    fēnix® 3

    Edge® 1000 / Explore

    D2Tm Delta/PX/S

    Edge® 1030

    fēnix® 3 HR

    Edge® 520

    DescentTm Mk1

    EnduroTm

    Forerunner® 230

    Edge® 130/Plus

    fēnix® 6 Series

    Forerunner® 235

    Edge® 520 Plus

    Forerunner® 245 Series

    Forerunner® 630

    Edge® 820

    Forerunner® 745

    Forerunner® 920XT

    fēnix® 5

    Forerunner® 945

    Forerunner® 45

    fēnix® 5 Plus Family

    Legacy Series

    tactix® Bravo

    fēnix® Chronos

    MARQ® Series

    quatix® 3

    Forerunner® 645 Series

    Montana 7 Series

    vívoactive®

    Forerunner® 935

    tactix® Delta Series

    GPSMAP 66 Series

    Venu®

    Oregon 7 Series

    Venu® Sq

    quatix® 5

    vívoactive® 4 Series

    Rino Series

    vívoactive® 3 Series

    Products running the latest system will get API updates and bug fixes. Products not running the latest system may receive updates when necessary.

    New in System 4

    The following features are new in the Connect IQ 4 SDK

    Get Started Today

    Use the Connect IQ SDK Manager to update your SDK and get the latest devices.

    • Apr 21, 2021
  • A Whole New World of Graphics with Connect IQ 4

    The smallwearable2021 and wearable2021 in the Connect IQ SDK manager represent the first products that support API level 4.0.0. We're releasing these device configurations in advance of launch so you can begin porting your apps to these new products and also try out the new capabilities of API level 4.0.0. This post covers some of the new graphics features available.

    Bitmap Packing Formats

    Images can grow your executable size, which can add extra wait when users install or update your app. To help reduce executable bloat, Connect IQ 4.0 introduces bitmap attribute for packing an image into your executable.

    ATTRIBUTE DEFINITION VALID VALUES DEFAULT VALUE NOTES
    packingFormat The format with which the image will be encoded into the PRG default, png, jpg, yuv. default Only available on certain devices

    Each of the formats can have their advantages and disadvantages:

    FORMAT ADVANTAGE DISADVANTAGE USE CASE
    default Available on all products, fastest to load, supports alpha channel No compression App runs on pre-Connect IQ 4.0 devices. Low palette images can have very small runtime costs
    png Lossless, compressed and supports alpha channel Slowest to load, which can add runtime cost if purged and reloaded frequently from the graphics pool Importing non-photo images with or without alpha channel
    jpg Compresses very well, fast to load Lossy format and does not support alpha channel Importing photo imagery without alpha channel
    yuv Compresses well, supports alpha channel, fast to load Lossy format Importing photo imagery with alpha channel

    Compile Time Image Scaling

    The resource compiler can scale your image resources at build time. Images can be scaled to a pixel size, to a percentage of their size, or to a percentage of the screen’s size.

    ATTRIBUTE DEFINITION VALID VALUES DEFAULT VALUE NOTES
    scaleX How should the image be scaled in the x dimension? Pixel size or percentage If scaleY is set, will default to scaleY’s value. Otherwise will default to 100% of image width See scaleRelativeTo
    scaleY How should the image be scaled in the x dimension? Pixel size or percentage If scaleX is set, will default to scaleX’s value. Otherwise will default to 100% of image height See scaleRelativeTo
    scaleRelativeTo What should the scale factor be based on? Screen or image Screen Sets what to base relative scaling on. If set to screen, image will be re-scaled based on product it is being built for at compile time

    Alpha Blending

    Connect IQ 4.0 adds some powerful new tools to the Dc:

    FUNCTION PURPOSE ACCEPTS API LEVEL
    Dc.setFill() Set fill tool for drawing primitives. Graphics.ColorType, Graphics.BitmapTexture 4.0.0
    Dc.setStroke() Set pen tool for drawing primitives Graphics.ColorType, Graphics.BitmapTexture 4.0.0
    Dc.setBlendMode() Set blend mode for drawing Graphics.BlendMode 4.0.0

    Previously, the setColor() API allowed the setting of a foreground or background color based on a 24-bit RRGGBB value. setFill() and setStroke() both accept 32-bit AARRGGBB values, allowing you to provide an alpha channel value with the RGB value. The setStroke() API allows setting the pen tool for the Dc, while setFill() sets the fill tool.

    You can also set the blend mode with setBlendMode(). By default, the system will blend your color with whatever is being drawn over. However, you can use BLEND_MODE_NO_BLEND to set the color and alpha of a BufferedBitmap directly. 

    In addition to colors, you can now also provide a BitmapTexture. This allows a primitive to be filled by a bitmap and opens up many new drawing possibilities.

    Graphics Pool

    In the past, all resources loaded at runtime into the application heap. This heap is used to hold your code, data, stack and runtime objects, so loading images could quickly limit the runtime functionality of your app.

    Connect IQ 4.0 introduces a new graphics pool that is separate from your application heap. When you load a bitmap or font at runtime, the resource will load into the graphics pool, and you will be returned a Graphics.ResourceReference.

    The graphics pool will dynamically cache, unload and reload your resources behind the scenes based on available memory. All the drawing primitives that accept resource objects also accept references, as well, so your app should not have to be reworked to take advantage of the new system.

    Calling ResourceReference.get() on a reference will return a resource object. As long as the object returned is in scope, the resource will be locked in the graphics pool.

    Buffered Bitmaps and the Graphics Pool

    BufferedBitmap objects, like other graphics resources, now take advantage of the graphics pool, as well. The advantage of this scenario is that you can now liberally use temporary graphics buffers without running out of application heap.

    As noted earlier, the graphics pool will intelligently purge and restore resources from the pool if the loaded resources exceed the available pool space. Unlike static resources that are reloaded from your executable, BufferedBitmap are not restored if they have been purged. This works fine if you are using a short-lived, temporary buffer, but if your bitmap is purged after allocation, you need to re-render its contents. Alternatively, you can call the get() method on the reference to get a locked version of the bitmap. This will prevent the BufferedBitmap object from being purged from the pool, but it can also lead to the graphics pool running out of available space if more resources are loaded.

    To create a BufferedBitmap, use the Graphics.createBufferedBitmap() API. If your application runs on pre-Connect IQ 4.0 devices, use a has check for allocating your BufferedBitmap:

    import Toybox.Graphics;
    
    //! Factory function to create buffered bitmap
    function bufferedBitmapFactory(options as {
                :width as Number,
                :height as Number,
                :palette as Array<ColorType>,
                :colorDepth as Number,
                :bitmapResource as WatchUi.BitmapResource
            }) as BufferedBitmapReference or BufferedBitmap {
        if (Graphics has :createBufferedBitmap) {
            return Graphics.createBufferedBitmap(options);
        } else {
            return new Graphics.BufferedBitmap(options);
        }
    }

    Putting it into Action

    To wrap our heads around these new APIs, let's create an animated watch face for the upcoming wearables. With our new alpha blending capabilities, we can have semi-transparent overlays drawn in real time. We are going to use this to create a fade to white effect to fade between multiple images.

    To start, we will add our images to resources:

    <drawables>
        <bitmap id="image1" filename="pretty_picture1.png" scaleX="100%" scaleRelativeTo="screen" packingFormat="jpg"/>
        <bitmap id="image2" filename="pretty_picture2.jpg" scaleX="100%" scaleRelativeTo="screen" packingFormat="jpg"/>
        <bitmap id="image3" filename="pretty_picture3.jpg" scaleX="100%" scaleRelativeTo="screen" packingFormat="jpg"/>
        <bitmap id="image4" filename="pretty_picture4.jpg" scaleX="100%" scaleRelativeTo="screen" packingFormat="jpg"/>
    
        <bitmap id="LauncherIcon" filename="launcher_icon.png" />
    </drawables>
    

    You'll need to pick four images (preferably with a square aspect ratio) to use. This takes advantage of both compile time image scaling to resize the image to the screen size as well as JPG image packing to reduce the resource size of the PRG. Next, we will create a standard layout:

    <layout id="WatchFace">
        <drawable id="Blinds" class="FadeDrawable"/>
    
        <label id="DateLabel" x="45%" y="5%" font="Graphics.FONT_SMALL" justification="Graphics.TEXT_JUSTIFY_RIGHT" color="Graphics.COLOR_BLACK" />
        <label id="TimeLabel" x="center" y="78%" font="Graphics.FONT_NUMBER_MEDIUM" justification="Graphics.TEXT_JUSTIFY_CENTER" color="Graphics.COLOR_BLACK" />
        <label id="BatteryLabel" x="55%" y="5%" font="Graphics.FONT_SMALL" justification="Graphics.TEXT_JUSTIFY_LEFT" color="Graphics.COLOR_BLACK" />
    </layout>

    I'm going to assume you can handle wiring up the date and battery fields. The FadeDrawable is where we will be doing all our work. This will be a drawable that uses WatchUi.animate()  to create the fade effect:

    import Toybox.Application;
    import Toybox.Application.Storage;
    import Toybox.Lang;
    import Toybox.Graphics;
    import Toybox.Timer;
    import Toybox.WatchUi;
    
    //! Class to execute a fade effect across multiple bitmaps
    class FadeDrawable extends WatchUi.Drawable {
    
        //! What part of the fade are we executing?
        enum State {
            STATE_FADE_IN,
            STATE_HOLD,
            STATE_FADE_OUT
        }
    
        //! Key for storing where we left off
        private const IMAGE_INDEX_KEY = "imageIndex";
    
        private var _imageArray as Array<Symbol>;
        private var _curImage as BitmapType?;
        private var _curImageIndex as Number;
        private var _state as State;
        private var _nextState as State;
        private var _timer as Timer.Timer;
    
        //! This variable is public so it can be modified by animate
        public var fade;
    
        //! Constructor
        //! @param options Layout options
        public function initialize(options as Dictionary) {
            Drawable.initialize({:identifier => options[:identifier]});
            // Array of images from resources
            _imageArray = [
                Rez.Drawables.image1,
                Rez.Drawables.image2,
                Rez.Drawables.image3,
                Rez.Drawables.image4
            ] as Array<Symbol>;
    
            // Restore the image index if we persisted it
            var index = Storage.getValue(IMAGE_INDEX_KEY);
            if (index instanceof Number) {
                _curImageIndex = index;
            } else {
                _curImageIndex = 0;
            }
    
            // Initialize values
            fade = 0;
            _state = STATE_FADE_IN;
            _nextState = STATE_FADE_IN;
            _timer = new Timer.Timer();
        }
    
        //! Handler to switch between states
        public function handleStateChange() as Void {
            _state = _nextState;
            switch(_nextState) {
                case STATE_FADE_IN:
                    // Clear the current image
                    _curImage = null;
                    // Load the next one
                    _curImage = Application.loadResource(_imageArray[_curImageIndex]);
                    // Advance the image count
                    _curImageIndex ++;
                    _curImageIndex = _curImageIndex % _imageArray.size();
                    // Persist where we are
                    Storage.setValue(IMAGE_INDEX_KEY, _curImageIndex);
                    // Kick off the animation
                    WatchUi.animate(self, :fade, WatchUi.ANIM_TYPE_EASE_OUT, 254, 0, 1, method(:handleStateChange));
                    _nextState = STATE_HOLD;
                    break;
                case STATE_HOLD:
                    // Wait three seconds
                    _timer.start(method(:handleStateChange), 3000, false);
                    _nextState = STATE_FADE_OUT;
                    fade = 0;
                    break;
                case STATE_FADE_OUT:
                    // Animate the fade back to white
                    WatchUi.animate(self, :fade, WatchUi.ANIM_TYPE_EASE_OUT, 0, 254, 1, method(:handleStateChange));
                    _nextState = STATE_FADE_IN;
                    break;
            }
        }
    
        //! Kick off the animation
        public function start() as Void {
            _state = STATE_FADE_IN;
            _nextState = STATE_FADE_IN;
            handleStateChange();
        }
    
        //! Terminate the animation
        public function stop() as Void {
            WatchUi.cancelAllAnimations();
            _timer.stop();
        }
    
        //! Draw the images
        public function draw(dc as Dc) as Void {
            var bmp = _curImage;
            // Make the fade color (white + alpha)
            var color = Graphics.createColor(fade.toNumber(), 255, 255, 255);
    
            if (bmp != null) {
                // Draw the bitmap
                dc.drawBitmap(0, 0, bmp);
                // Draw the fade on top
                dc.setFill(color);
                dc.fillRectangle(0, 0, dc.getWidth(), dc.getHeight());
    
                // Draw the semi-transparent circles above and below
                // to offset the time text
                dc.setFill(0x80FFFFFF);
                dc.setStroke(0x80FFFFFF);
                dc.fillCircle(dc.getWidth() / 2, dc.getHeight() * 1.5, dc.getWidth() * .7);
                dc.fillCircle(dc.getWidth() / 2, -(dc.getHeight()/2), dc.getWidth() * .7);
            }
        }
    }
     

    Here is the watch face in action, featuring appearances by Commodore the dog:


    A Whole New World

    As you can see the new APIs open a lot of possibilities to add graphical "oomph" to your apps. The APIs are available and the products are coming, so now is the time to think about how you can get your apps ready.

    • Apr 19, 2021
  • The Road to Strict Typing is Paved with Good Intentions

    Meme via u/BenSteamRoller

    The Monkey C language was introduced in 2014 as an object-oriented, memory-managed, duck-typed language. While it has a robust class system, the compiler did not perform any compile time type checking of the code. This allows for very flexible code, but also makes it difficult to identify type issues until run time. Every Connect IQ developer is familiar and annoyed with the Symbol Not Found error, which happens when you attempt to access a value not present in an object. Connect IQ 4 introduces Monkey Types, a new type system for Monkey C. Monkey Types has four levels of type-check, from none to strict, to allow developers to choose their style of programming.

    In this piece we will take you down the journey of taking a watch face from duck typing to strict typing. Along the way we will introduce the new syntax you can use to type your applications.

    The Four Levels of Type Safety

    A compiler type checker is inherently the judgiest of software. Its job is to tell you why it can’t understand your beautiful musings. It is computer software where you can set how pedantic you want it to be.

    As stated above, Monkey Types provides four levels of type-checking:

    Level

    Description

    Judginess

    Work

    None

    Default. Same checks as performed today

    Supes-chill dude. Does it compile? Then I’m sure it runs!

    Does your code compile? Then you don’t have any work to do

    Gradual

    Type-checker will only check type safety when interacting with typed code.

    Assumes you’re a good student. Will highlight errors it sees, but assumes anything it can’t understand is fine

    Requires adding some clarity to calls, but in an hour a watch face can be converted to compile at this level

    Informative

    Type-checker will only check type safety when interacting with typed code but will warn you about untyped code.

    More passive aggressive than judgy. Points out everything it can’t check but ends with “but if you say it’s okay then who am I to judge”. Is probably sub-tweeting about you.

    Same as Gradual, but with more drama.

    Strict

    Type-checker expects all definitions to be typed.

    Pretty judgy. Gets upset if it can’t check your code.

    For a new code base, strict typing requires minimal effort and encourages defensive coding. For an existing code base, it can take some effort to convert to strict typing.

    Gradual Type-Checking

    Let’s start with a very simple watch face:

    using Toybox.WatchUi;
    using Toybox.Graphics;
    using Toybox.System;
    using Toybox.Lang;
    
    class TypeCheckFaceView extends WatchUi.WatchFace {
    
        function initialize() {
            WatchFace.initialize();
        }
    
        // Load your resources here
        function onLayout(dc) {
            setLayout(Rez.Layouts.WatchFace(dc));
        }
    
        // Called when this View is brought to the foreground. Restore
        // the state of this View and prepare it to be shown. This includes
        // loading resources into memory.
        function onShow() {
        }
    
        // Update the view
        function onUpdate(dc) {
            // Get and show the current time
            var clockTime = System.getClockTime();
            var timeString = Lang.format("$1$:$2$", [clockTime.hour, clockTime.min.format("%02d")]);
            var view = View.findDrawableById("TimeLabel");
            view.setText(timeString);
    
            // Call the parent onUpdate function to redraw the layout
            View.onUpdate(dc);
        }
    
    }
    

    This is the standard template for a new watch face and compiles perfectly fine with no type checking. Now we will turn up the type-check level in Visual Studio Code by going to the extension settings and setting the type check level:

    Now when we build the current project, we have a type error on the following lines:

    ERROR: fenix5plus: TypeCheckFace\source\TypeCheckFaceApp.mc:19: Object of type '$.Toybox.Lang.Array<Any>' does not match return type 'PolyType<Null or $.Toybox.Lang.Array<$.Toybox.WatchUi.InputDelegate or $.Toybox.WatchUi.View>>'.

    ERROR: fenix5plus: TypeCheckFace\source\TypeCheckFaceView.mc:29: Cannot find symbol ':setText' on type 'PolyType<Null or $.Toybox.WatchUi.Drawable>'.

    At this point you might say “wait, you told me that gradual typing only type checks typed code. I haven’t added any types, so why is it flagging errors?”. That is a good question, and can be answered with three points:

    1. While you haven’t added any typing to your code, we added type information to the Monkey C API.
    2. Monkey Types type inferencing of local variables allows it to track types on API calls return values.
    3. Monkey Types will auto-apply the typing from a parent class to overridden functions of a subclass.

    Even though you haven’t decorated your code with type information, Monkey Types sees that you’ve extended WatchUi.WatchFace and you’ve overridden View.onUpdate, and it has some things to say about that.

    So how do we clear this up? It’s time to introduce the first two concepts of Monkey Types: casting and typed containers.

    Casting Types

    In our TypeCheckFaceView.onUpdate() function Monkey Types can detect we are calling View.findDrawableById() which returns a value of type WatchUi.Drawable. However, WatchUi.Drawable does not have a function named setText(); that belongs to WatchUi.Text. We know that view is a Text, so let’s clear that up for the type checker:

            var view = View.findDrawableById("TimeLabel") as WatchUi.Text;

    Because of its duck-typed nature casting in Monkey C before Monkey Types was unnecessary, but now we need to a way to communicate these kinds of misunderstandings to the type system. The as keyword is the new keyword for both type application and type casting. Within a function the as keyword behaves like a casting operator, with the value to cast on the left and the type to cast it to on the right.

    At this time type casts, are not type-checked at compile time. Type casts allow you to tell the type checker that you know better, but in the future there will be compile time type cast checking.

    Typed Containers

    Monkey Types now allows container types to be typed. You can use the following syntax to create a new typed Array or Dictionary:

    Syntax

    Description

    var a = [1, 2, 3] as Lang.Array<Lang.Number>

    a is an Array that only accepts values Number types.

    var d = { “key”=>”value” } as Lang.Dictionary<Lang.String, Lang.String>;

    d is a Dictionary that only accepts keys of type String and assigns values of type String.

    var s = new Lang.Array<Lang.String or Null>[10];

    s is a ten element Array that only accepts values of type String.

     

    Adding a type with as at container declaration is different than casting a value. When you add a type to an Array or Dictionary declaration, the type checker will validate all assignments to the container. If you apply the as clause to the local, it is a type cast. Here is a disambiguation:

    Typed Container

    Type Cast

    Type Casting

    var a = [] as Lang.Array<Lang.String>;

    var a = [];
    return a as Lang.Array<Lang.String>

    By this point you might be thinking “wow those module prefixes are going to get super annoying”. Don’t worry, we thought that too, and we will get to that later.

    The problem we have is that in our application class AppBase.getInitialValue() now expects it’s return type to be a typed array. We can easily change the declaration, but we must add the new modules we are referencing to the using statements:

    using Toybox.Application;
    using Toybox.WatchUi;
    using Toybox.Lang;
    
    class TypeCheckFaceApp extends Application.AppBase {
    
        function initialize() {
            AppBase.initialize();
        }
    
        // onStart() is called on application start up
        function onStart(state) {
        }
    
        // onStop() is called when your application is exiting
        function onStop(state) {
        }
    
        // Return the initial view of your application here
        function getInitialView() {
            return [ new TypeCheckFaceView() ] as Lang.Array<WatchUi.View or WatchUi.InputDelegate>;
        }
    
    }
    

    With those changes, we now pass gradual type checking.

    It’s good to review here what the type checker knows and doesn’t know. By default, any local or function parameter is of type Any. The Any type is the traditional Monkey C value type. It can be anything, or it can be nothing (null). At the gradual level, the type checker will not attempt to type check calls against values of type Any. Instead it will check the things it can infer:

    1. The result of a new operation
    2. The result of creating a container
    3. The return value of a Toybox module function
    4. A typed function parameter, either explicitly type or type-inferred from the function it overrides

    Within your onUpdate() function, any call you make to dc will be type checked, catching typos and type issues that would before would require a test run to find. Because the API is typed this covers a lot of ground but can also leave big gaps in what can be checked. For many projects this is a perfectly acceptable level of type checking; it allows you to check calls against the Toybox API without forcing you to refactor the code.

    But how big is the type check gap? What does it not know?

    Informative Typing

    Informative typing will issue the same errors as gradual typing, but will warn about any untyped member variable, function argument, or return value.

    When we up the type-check level to informative on our watch face, we get the same output as before. This is because all our functions override typed function definitions, and we don’t have any member variables. However, doing a findDrawableById() on every update is a little slow. We should cache that value in onLayout() so we only have to do the lookup once:

    using Toybox.WatchUi;
    using Toybox.Graphics;
    using Toybox.System;
    using Toybox.Lang;
    
    class TypeCheckFaceView extends WatchUi.WatchFace {
        private var _timeLabel;
    
        function initialize() {
            WatchFace.initialize();
        }
    
        // Load your resources here
        function onLayout(dc) {
            setLayout(Rez.Layouts.WatchFace(dc));
            _timeLabel = View.findDrawableById("TimeLabel");
        }
    
        // Called when this View is brought to the foreground. Restore
        // the state of this View and prepare it to be shown. This includes
        // loading resources into memory.
        function onShow() {
        }
    
        // Update the view
        function onUpdate(dc) {
            // Get and show the current time
            var clockTime = System.getClockTime();
            var timeString = Lang.format("$1$:$2$", [clockTime.hour, clockTime.min.format("%02d")]);
            _timeLabel.setText(timeString);
    
            // Call the parent onUpdate function to redraw the layout
            View.onUpdate(dc);
        }
    }

    If we compile this at the informative level, we get the following:

    WARNING: fenix5plus: source\TypeCheckFaceView.mc:7: Member '$.TypeCheckFaceView._timeLabel' is untyped.

    This may seem like the type checker is stating the obvious. Yes, the value is untyped, but why can’t it just infer the value from the assignment like it magically did for locals? The challenge with a member variable is that it can be assigned within any function inside (or potentially outside) the class, and tracing all potential assignments is a level of static analysis the compiler is not going to attempt. What the compiler is saying is “you didn’t type this, so I won’t be able to validate any reference you make to this value. If you’re cool with that, I’m cool with it”.

    You can use the as keyword (hi again!) to add type information to member declarations:

    Syntax

    Description

    var a as String or Null;

    a is a member variable that only accepts values of type String or null

    function example(x as String) as Void

    example() is a function that takes a parameter x which must be a String. It has no return value

    function getValue() as String?

    getValue() is a function that returns either a String or null.


    Let’s recompile with the following change:

    private var _timeLabel as WatchUi.Text;

    Don’t forget to add the cast to the assignment of _timeLabel in onLayout(). When we compile we now get the following:

    ERROR: fenix5plus: source\TypeCheckFaceView.mc:7: Member _timeLabel not initialized but does not accept Null.

    Monkey Types requires you to declare if a member variable can be null. If it can’t be null, you must initialize the value at declaration or in the initialize function. In our case we can’t initialize _timeLabel until onLayout(), so we have to allow it to be null. With a single type declaration, we can allow null by adding a question mark:

        private var _timeLabel as WatchUi.Text?;

    Now the compiler is happy once again.

    Import Statements

    As you start adding typing to your functions and variables, you will quickly tire of adding all the module prefixes. We didn’t want to change the behavior of using statements, so Monkey Types introduces the new import statement. For functional code import works the same as using, but for type scaffolding import brings the class definitions of the module into the namespace. If we change our using statements to import statements:

    import Toybox.WatchUi;
    import Toybox.Graphics;
    import Toybox.System;
    import Toybox.Lang;
    

    We can now define _timeLabel as:

        private var _timeLabel as Text?;

    One big difference between import and using statements is that import doesn’t allow renaming the module using as. This is an intentional change; module renaming has negative impacts on code readability as common modules can be renamed per file.

    Strict Typing

    With strict typing, the compiler requires all member variables, function parameters, and function return values to be typed. At this point you may say “not every part of my code can be typed as a set of defined classes, and it will take some refactoring to squeeze it into that definition”. Thankfully Monkey Types has a whole new set of typing tools to assist:

    Type

    Description

    Syntax

    Example

    Concrete Types

    Any class in the code namespace

    <Class Name>

    var x as Number;

    Void

    Only allowed as a return value, communicates that function does not return a value

    Void

    function doIt() as Void

    Poly Types

    A list of n allowed types a value accepts

    Type or Type

    var x as Number or String;

    Interface

    A value must include the listed definitions to match. All definitions are public.

    interface {

      var <name> (as Type);
      
    function <name>(<arguments>);

    }

    function doIt(x as interface {
      function whatToDo();
    })

    Container

    A typed Array or Dictionary

    Array<Type>
    Dictionary<Type, Type>

    var x as Array<Number>;

    Dictionary

    Type checks the values a dictionary accepts to a certain set of keys

    {

      [<literal>=>Type],

    }

    function doIt(options as {

      :key1=>String,

      :key2=>Number

    })

    Enumerations

    A named enum definition

    enum <Name> {

    }

    enum Values {

      VALUE_1,

      VALUE_2

    };

    Callback

    A typed Method

    Method(<arguments>) [as <Type>]

    var x as Method(a as String) as Void;

    Null

    Accepts a null value

    Null

    var x as String or Null;

    Named Types

    Allows you to give a namespace definition to a commonly used type

    typedef <Name> as Type;

    typedef NumberArray as Array<Number>

     

    You can read more about the types in the Monkey Types section of the documentation. Do you have a set of classes that fit into an interface pattern? Now you can define an interface it has to match. Do you use Method objects? Now they can be type checked. Do you have enumerations you pass as parameters? Now they can be type checked.

    Monkey Types only exist at compile and do not add any runtime baggage to your app. While this means you aren’t penalized for typing your app, it also means that instanceof and has still only function with class definitions and symbols, respectively.

    Defensive Coding

    Let’s say we wanted to add the current heart rate to this watch face. Rather than add a layout, we’re going to add a draw call. Let’s create a function to draw the heart rate at the top of the screen:

        function drawHeartRate(dc as Dc, heartRate as Number) as Void {
            dc.drawText(dc.getWidth() / 2, 0, Graphics.FONT_SMALL, heartRate.toString(), Graphics.TEXT_JUSTIFY_CENTER);
        }
    

    Now let’s add something at the tail end of our onUpdate() (don’t forget to import Toybox.Activity):

            // Call the parent onUpdate function to redraw the layout
            View.onUpdate(dc);
    
            var hr = Activity.getActivityInfo().currentHeartRate;
            drawHeartRate(dc, hr);
    

    When we compile we get the following:

    ERROR: fenix5plus: TypeCheckFace\source\TypeCheckFaceView.mc:37: Passing 'PolyType<Null or $.Toybox.Lang.Number>' as parameter 2 of non-poly type '$.Toybox.Lang.Number'.

    The value ActivityInfo.currentHeartRate value can be a Number or it can be null, but our function only accepts Number. The compiler is letting us know that we aren’t handling this case. To fix this we could use a type cast, but that’s not really fixing the fundamental issue. Instead lets change the onUpdate() code to the following:

            // Call the parent onUpdate function to redraw the layout
            View.onUpdate(dc);
    
            var hr = Activity.getActivityInfo().currentHeartRate;
            if (hr != null) {
                drawHeartRate(dc, hr);
            }
    

    And the compiler is now happy. This is an example of an if-split. With local variables, the compiler will examine the if expression for a type test and temporarily re-type a value in the true and false case. You can also do the following:

            View.onUpdate(dc);
    
            var hr = Activity.getActivityInfo().currentHeartRate;
            if (hr instanceof Number) {
                drawHeartRate(dc, hr);
            }
    

    With Monkey Types the compiler is much more aggressive in telling you where you have potential type and null pointer issues and encourages you to fix it with more defensive code. At the strict type check level, it can validate all your functional code and the calls to the API.

    Let’s take a final look at our watch face view class with all the typing information:

    import Toybox.Activity;
    import Toybox.WatchUi;
    import Toybox.Graphics;
    import Toybox.System;
    import Toybox.Lang;
    
    class TypeCheckFaceView extends WatchUi.WatchFace {
        private var _timeLabel as WatchUi.Text?;
    
        function initialize() {
            WatchFace.initialize();
        }
    
        // Load your resources here
        function onLayout(dc as Dc) as Void {
            setLayout(Rez.Layouts.WatchFace(dc));
            _timeLabel = View.findDrawableById("TimeLabel") as WatchUi.Text;
        }
    
        // Called when this View is brought to the foreground. Restore
        // the state of this View and prepare it to be shown. This includes
        // loading resources into memory.
        function onShow() as Dc {
        }
    
        // Update the view
        function onUpdate(dc as Dc) as Void {
            // Get and show the current time
            var clockTime = System.getClockTime();
            var timeString = Lang.format("$1$:$2$", [clockTime.hour, clockTime.min.format("%02d")]);
            _timeLabel.setText(timeString);
    
            // Call the parent onUpdate function to redraw the layout
            View.onUpdate(dc);
    
            var hr = Activity.getActivityInfo().currentHeartRate;
            if (hr instanceof Number) {
                drawHeartRate(dc, hr);
            }
    
        }
    
        // Draw the heart rate
        function drawHeartRate(dc as Dc, heartRate as Number) as Void {
            dc.drawText(dc.getWidth() / 2, 0, Graphics.FONT_SMALL, heartRate.toString(), Graphics.TEXT_JUSTIFY_CENTER);
        }
    
    }
    

    Which Type Level Makes Sense?

    It is not a requirement to enable Monkey Types. The gradual type level is intended for bringing the advantages of type checking with minimal effort to an existing code base and is effective at finding many type errors with little additional type scaffolding. If you have an existing project that has achieved code stability and you mostly add support for new devices, the cost of getting it to compile under strict type check may not be worth it, but if you begin a project at the strict typing level, it is straightforward to add the type definitions as you go, and the compiler will keep you honest.

    • Apr 16, 2021
  • Connect IQ 4.0 Preview 3 SDK

    We released the Connect IQ 4.0 Preview 3 SDK  today, which includes several fixes for issues identified in earlier preview releases. We've also released two 4.0-compatible wearable device configurations (Upcoming Wearable and Upcoming Small Wearable), which you can use to start updating your own apps for future 4.0-compatible products. The Preview 3 SDK is required to use these devices, so be sure to install both!

    You can get the Connect IQ 4.0 Preview 3 SDK and the upcoming wearable configurations today using the Connect IQ SDK Manager (make sure you have the latest version). We also have a sample on Github that demonstrates Monkey Types features.

    For more information on some of the features available in the 4.0 SDK, please take a look at the Welcome to Connect IQ 4.0 announcement. If you have any feedback on these new features, we’d love to hear it. Please drop us a line in the forums so we can see what you think.

    • Apr 13, 2021
  • Garmin Developer Virtual Conference is back October 13, 2021

    Join us virtually on October 13, 2021 to learn about all things Garmin Developer Programs - including major developer announcements and information for the year ahead.

    Similar to last year, this free virtual event will have presentations and keynotes, breakouts and the opportunity to engage with our Garmin team in Q&A.

    More details will be available in the months ahead on developer.garmin.com/gdvc or right here on the Connect IQ Developer Forum. In the meantime, remember to mark the date - October 13, 2021 - in your calendars.

    Thanks to those who attended GDVC 2020, we reached more developers and partners than ever - engaging and connecting with people from around the world. If you missed last year’s event, don’t worry, there’s still time to view our breakout videos.

    Stay informed on all things GDVC, join our mailing list!

    • Apr 8, 2021
  • Enduro Now Available in Connect IQ SDK Manager

    The EnduroTmhas been added to the Connect IQ supported device list. Featuring a Power GlassTm solar charging lens that extends battery life and packed with top performance features, the Enduro GPS watch is built for extreme endurance athletes.

    Use the Connect IQ SDK manager to update your device library and add Enduro support to your apps.

    • Feb 16, 2021
  • Welcome to Connect IQ 4.0

    At the Garmin Developer Virtual Conference this year, we shared some of the new features coming to Connect IQ 4.0 and also released a preview of the new Visual Studio Code extension. Today we have released a first preview of the Connect IQ 4.0 SDK. Let’s dive a little deeper into the new features you can expect from Connect IQ 4.0.

    Monkey Types

    Monkey Types is the new gradual type system for Monkey C, and this release has the first developer preview of the feature. Monkey Types is turned off by default for language compatibility, but you can turn it on by going to the compiler options settings in Eclipse or Visual Studio Code and adding ‘-l 1’.

    As an example, here is a watch face onUpdate function using Monkey Types:

        // Update the view
        function onUpdate(dc as Dc) as Void {
            // Get and show the current time
            var clockTime = System.getClockTime();
    
            var info = System.getDeviceSettings();
            var hour = clockTime.hour;
    
            if(!info.is24Hour) {
                hour = hour % 12;
                hour = (hour == 0) ? 12 : hour;
            }
    
            var timeString = Lang.format("$1$:$2$", [hour.format("%02d"), clockTime.min.format("%02d")]);
            
            _timeLabel.setText(timeString);
    
            var date = Gregorian.info(Time.now(), Gregorian.FORMAT_SHORT);
            var dateString = Lang.format("$1$/$2$/$3$", [(date.month as Number).format("%02d"), date.day.format("%02d"), date.year.format("%02d")]);
            _dateLabel.setText(dateString);
    
            // Call the parent onUpdate function to redraw the layout
            View.onUpdate(dc);
        }

    If this looks remarkably like what you’re used to, that’s the point. Monkey Types uses type-inferencing to identify types when possible, and it will only type-check what it knows about (including the API). It does require function arguments, return values and member variables to have typing information added using the “as” keyword.

    The “as” keyword also does double-duty as the casting operator, which you can see on this line:

    var dateString = Lang.format("$1$/$2$/$3$", [(date.month as Number).format("%02d"), date.day.format("%02d"), date.year.format("%02d")]);
            _dateLabel.setText(dateString);

    The reason we need to cast month is that it can be a Number or a String, but only a Number has the format method. Monkey Types can see that without having to cast date as a Gregorian.Info.

    We think Monkey Types will make it much easier to find type errors. It also does not add any runtime cost and will work for code for all Connect IQ devices. We reserve the right to change the grammar or behavior, so don’t make any irreversible changes to your code based on this preview. Look in the Monkey C section of the documentation included in the SDK for more information.

    Resource Compiler Updates

    Traditionally, Garmin wearables have featured always-on displays, but limited color palettes to utilize for your Connect IQ apps. That all changed with the Venu®, which introduced the first AMOLED display for a Garmin wearable. We’ve brought a few changes to Connect IQ 4.0 to make it easier for you to support the wide range of displays in Connect IQ.

    First, the resource compiler now supports SVG as a standard import format. SVG is a popular vector format that is supported by a number of vector editing programs. SVGs are importable using the bitmap tag, which makes it easier to import complex vector imagery that scales across multiple products.

    Speaking of scaling, the bitmap tag also supports three new options for scaling a bitmap on import:

    Attribute

    Value

    Notes

    scaleX

    Pixel value or Percent

    Can be independent of scaleY. If scaleY is set but not scaleX, will assume scaleX = scaleY

    scaleY

    Pixel value or percent

    Can be independent of scaleY. If scaleY is set but not scaleX, will assume scaleX = scaleY

    scaleRelativeTo

    “screen” or “image”

    If scaleX or scaleY is a percentage, will compute the percentage relative to the screen resolution or the image resolution

    For example, if you want an image to be your header and take up the top 10% of the screen, you can use the syntax:

    <bitmap filename="example.svg" id="example" scaleY="10%" scaleRelativeTo="screen" />

    This will perform image scaling to account for the different watch face sizes on a vívoactive® 4s and a Venu. When combined with SVG, this makes it much easier to make layouts that scale across products.

    Get the Preview

    You can get the Connect IQ 4.0 preview SDK today using the Connect IQ SDK Manager (make sure you have version 1.0.2 of the SDK Manager). We have a sample on Github that demonstrates Monkey Types.

    If you have any feedback on these new features, we’d love to hear it. Please drop us a line in the forums so we can see what you think.

    • Feb 2, 2021
  • Dark Mode is the New Black

    The next version of the Connect IQ Store is adding a dark theme for the iOS and Android apps. Now your app icons can be presented in dark mode against a dark background (#252525) instead of the standard light theme (#FBFBFB).

    To make your apps look their best in dark mode, we have a few tips for you to try. If your app icon has a transparency, make sure to test it against both light and dark backgrounds to make sure it is visually pleasing. For app icons with a light background fill, we recommend rounding the outer corners to make them look better against a dark background.

    These changes will be coming to the Connect IQ app in early February, so now is your chance to try out dark mode and make your icons pop at launch.

    • Jan 22, 2021