Made some progress on publishing complications!

This is with the 4.2.0 beta1 SDK, the f7 System 6 preview device, and one of my watch apps.  Here's what I see in the sim under Simulation>Complications, when I select the complication.  The app is running in the sim at this point.  The image is the icon I supplied for the complication.  53.10 is the temperature as seen by the app.  A first step!

  • The point YOIU missed is today, the id nor the UUID is used

    Are you sure about that?

    I haven't tried, but from reading the doc, it seems to me that you have to provide the complication ID (Toybox.Complications.Id) when you subscribe to a complication. Whether you get that ID by manually constructing it (native complications only) or iterating through a list which has metadata (e.g. shortLabel/longLabel) is irrelevant.

    The ID in the resource file actually is relevant - if you read the docs, you'll see that Garmin says you shouldn't change it.

    If you stop to think about how this works internally, you'll see why - if an app subscribes to a complication published by another app, and persists the subscribed ID, if you change the resource iD, that subscription will no longer be valid.

    Again I'm assuming that a Toyboy.Complication.Id consists of:

    - complication type (invalid/CIQ or native type)

    - CIQ app uuid (or some other way to refer to the app)

    - complication id in CIQ resource file

    Not sure how else subscribing to a complication would work if it doesn't use Toybox.Complication.Id and that Id doesn't consist of the CIQ app UUID (or some other reference) and the complication ID within the resource file.

    Note the the shortLabel and longLabel don't seem to have to be globally unique, nor do you pass them in to the function that subscribes to complications.

    I mean sure, you could avoid using Complication IDs by simply using the iterator and grabbing the complication you're interested in based on shortLabel/longLabel. Not sure how you would persist the selected complication between application runs (for example) if you don't store the ID. And not sure how it would work internally if the ID isn't significant.

  • Also note that _psx_ specifically said *subscriber*. Not just an app that casually iterates through Complications without subscribing. Pretty sure you need the ID to subscribe.

  • Yes, I'm sure. I've tried this stuff and gotten it to work.  Today, subscribeToUpdates() doesn't even compile!

    The doc for this is pretty bad.  In the code you posted, you need to look at how the complicationID is put inStorage and what it actually is.

  • In the code you posted, you need to look at how the complicationID is put inStorage and what it actually is

    I assume the complication ID was originally obtained by iterating through installed complications and allowing the user to select one.

    Just using common sense, it would seem to it should consist of the complication type, some way to refer to the originating app (either UUID or unique reference on the given device), and the complication "ID" in resource file. I'm assuming that Toybox.Complications.Id is globally unique (or at least locally unique with respect to the device.)

    If that isn't the case, it's either broken or incomplete (since this is a beta)

    For example, maybe today it's some combo of shortLabel/longLabel. If that's the case, that would be terrible since there would be no way to distinguish between complications which have the same label but came from different apps.

    The doc for this is pretty bad.

    So what else is new?

    Today, subscribeToUpdates() doesn't even compile!

    So maybe the feature isn't complete? Seems like we can't even talk about "subscribers" if we can't subscribe to a complication. Not sure how you can say that the complication ID in resource is only used for publishing if this stuff isn't even complete yet. Clearly Garmin intends for it to significant for subscribers at *some* point since the doc tells you not to change it or you'll break subscriptions.

    I just described my educated guess on how I think it should work. And my main point was that it wouldn't be logical for the complication ID in resource file (0-255) and Toybox.Complications.Id to be identical.

  • Look at this whole thread.  I've been saying the doc is bad, filled with typos, etc.

    Yes to get into Storage, the iterator gets walkeds.  Just as I'm doing!

    Maybe instead of an "educated guess" you need to TRY things before you correct others who have it working! Disappointed

  • I'm kind of curious to try complications now, although it seems incomplete. Sorry for contradicting you.

    It seems to me that the *intent* (based on reading the docs), is that Toybox.Complications.Id is supposed to be unique (either globally or for a given device), and that the complication resource ID (0-255) is meant to be unique per CIQ app (much like a FIT field ID). That was really all I wanted to say.

    If it's currently the case that Toybox.Complications.Id doesn't have any kind of reference to the publishing app and that the complication resource ID isn't used, I have to admit I obviously don't know either way (I did say I didn't try this yet), but that would be very strange to me, especially if it's still the case once the feature is ready for production.

    Yes to get into Storage, the iterator gets walkeds.  Just as I'm doing!

    Right, and my point was that you're still using Toybox.Complications.Id, whether you're storing it, using it to subscribe to updates, or both. The fact that the user only sees shortLabel/longLabel (if you display it in your interface) is sort of irrelevant. The question is about how the subscriber distinguishes between different complications. You can't just say "use shortLabel/longLabel" imo because those aren't unique (are they?). Of course the deeper question is how the API/firmware distinguishes between complications when you subscribe to them (and when you iterate through them and it gives you the ID as part of the returned data)

    Again my main point - in response to _psx_'s comment - was that it wouldn't be logical for the per-app complication resource ID (0-255) and Toybox.Complications.Id to be identical. From there I speculated on what I think what would need to go into Toybox.Complications.Id to turn it into a unique reference to a given complication. Obviously the type has to be in there (since getType() is a method on the Id). If you're telling me the CIQ app UUID (or some other app reference) and the complication resource ID aren't in there, I believe you, but it doesn't make sense to me, unless this stuff isn't finalized yet.

  • After I got this all working, I went back to watch the video from the GDVC on complications to make sure I wasn't missing something.  The same typos as are in the docs were on the slides, and next to nothing was said about publishing or using CIQ complications.

  • So I tried it out myself to put my money where my mouth is.

    Here's an excerpt of my complication publisher:

    <iq:application id="d394395d-3b51-414c-8143-b5cb498e534a" type="watch-app" name="@Strings.AppName" entry="complApp" launcherIcon="@Drawables.LauncherIcon" minApiLevel="4.2.0">

    ...

    <strings>
        <string id="AppName">compl</string>
    
        <string id="prompt">Click the menu button</string>
    
        <string id="menu_label_1">Item 1</string>
        <string id="menu_label_2">Item 2</string>
    
        <string id="label1">label 1</string>
        <string id="label2">label 2</string>
    </strings>
    

    <complications>
        <complication id="1" access="public"
            longLabel="@Strings.label1"
            shortLabel="@Strings.label1"
            icon="@Drawables.complicationIcon">
            <faceIt defaultText="@Strings.label1" />
        </complication>
        <complication id="2" access="public"
            longLabel="@Strings.label2"
            shortLabel="@Strings.label2"
            icon="@Drawables.complicationIcon">
            <faceIt defaultText="@Strings.label2" />
        </complication>
    </complications>

    Complication subscriber:

        var complication1, complication2;
    
        // onStart() is called on application start up
        function onStart(state as Dictionary?) as Void {
            var iter = Complications.getComplications();
            // iterate over them
            var complication = iter.next();
    
            while (complication != null) {
                System.println("short label: " + complication.shortLabel);
                System.println("ID: " + complication.complicationId.toString());
                if ("label 1".equals(complication.shortLabel)) {
                    complication1 = complication;
                }
                if ("label 2".equals(complication.shortLabel)) {
                    complication2 = complication;
                }
                complication = iter.next();
            }
        }

    Memory viewer in the subscriber:

    As you can see, the complication ID (Toybox.Complications.Id) has the following elements, exactly as I'd guessed:

    - native type

    - app uuid

    - complication ID within app resource file (here it's called an "index", which is a much better name).

    Idk what to tell you man. My educated guess was right, before writing a single line of code. Not that I haven't been wrong a million times before, but it's not like I'm just randomly guessing. I read the docs, use my experience as a dev, and apply logic/common sense. And I always try to explain my reasoning, so you're free to poke holes in my logic or assumptions.

    I was careful to note that I hadn't tried it, in my original comment.

    Again, to be clear, of course you don't need to use the complication ID if you're just iterating through complications. But the system uses it, and you need it if you want to eventually subscribe to a complication.

    And it answers the question of how a subscriber distinguishes between complications (even when the ID in resource file is the same). The ID in the resource is only unique for that app (just like FIT fields). (By "subscriber", I mean the internal code that does the subscribing, not what can be shown in the UI or is publicly available to CIQ apps. It's clear that the contents of Toybox.Complication.Id are purposely opaque to CIQ apps, except for the native type.)

    I did notice that the compiler seems to want complication indices to be contiguous (and start at 1). That kind of sucks because it means there's no good way to deprecate a complication that your app has published (because if you subsequently create a new complication, you'll have to reuse the resource index, which means reusing the unique ID of the old complication.) I filed a bug report: forums.garmin.com/.../compiler-insists-that-compilation-resource-ids-indices-are-contiguous-and-start-at-1

    EDIT: Nope, I was wrong about that ^ (thanks )

    EDIT:

    To be 100% clear, I realize there's no way to access the app uuid and resource index of a complication in such a way that you can use it to programmatically select a complication or display that data to the user, since it doesn't seem to be publicly exposed in the API. That's not the point. The point is to understand what a Toybox.Complication.Id actually is, since it uniquely identifies complications and it's used to subscribe to complications. For example, if you change the resource ID of an already-published complication, it will clearly break existing subscriptions (where the subscribing app stores the complication ID for reuse), as noted in the documentation.

  • getComplication(id as Complications.Id) as Complications.Complication or Null

    So id is only for build in complication? Or I can use id to fetch CIQ compilation?

  • So id is only build in complication? Or there is I can use id to fetch CIQ compilation?

    Complications.Id is for both built-in (native) and CIQ complications, but the API only exposes the native type for the Id object, even though the object contains native type, app UUID, and resource index.

    There's only two documented ways to obtain an Id:

    - Use the constructor to create the ID for a native type.

    - Use getComplications() to iterate through all built-in complications. You get a list of Complications, which contains the Id, icon, short label and long label, among other things

    So even though you can see the app UUID and resource index for a CIQ Complications.Id in the memory viewer (as in the above post), you can't access them with your code (unless the documentation is wrong).

    This means that you don't have any supported way to programmatically select your own app's complication if you know the app UUID and resource index, for example.

    Again I'm going to speculate, but it seems obvious the design was purposely constructed so:

    - you (a dev of a subscriber app) don't have access to the app uuid and resource index

    - you can create a UI to show the user the icon and name (short or long) of each complication on the system, so they can select a complication to subscribe to

    But just because we don't have access to the app UUID and resource index doesn't mean they aren't internally used as part of the identifier.