pebble/devsite/source/_guides/migration/migration-guide-3.md
2025-02-24 18:58:29 -08:00

16 KiB

title description permalink generate_toc guide_group order
SDK 3.x Migration Guide Migrating Pebble apps from SDK 2.x to SDK 3.x. /guides/migration/migration-guide-3/ true migration 2

This guide provides a detailed list of the changes to existing APIs in Pebble SDK 3.x. To migrate an older app's code successfully from Pebble SDK 2.x to Pebble SDK 3.x, consider the information outlined here and make the necessary changes if the app uses a changed API.

The number of breaking changes in SDK 3.x for existing apps has been minimized as much as possible. This means that:

  • Apps built with SDK 2.x will continue to run on firmware 3.x without any recompilation needed.

  • Apps built with SDK 3.x will generate a .pbw file that will run on firmware 3.x.

Backwards Compatibility

Developers can easily modify an existing app (or create a new one) to be compilable for both Pebble/Pebble Steel as well as Pebble Time, Pebble Time Steel, and Pebble Time Round by using #ifdef and various defines that are made available by the SDK at build time. For example, to check that the app will run on hardware supporting color:

#ifdef PBL_COLOR
  window_set_background_color(s_main_window, GColorDukeBlue);
#else
  window_set_background_color(s_main_window, GColorBlack);
#endif

When the app is compiled, it will be built once for each platform with PBL_COLOR defined as is appropriate. By catering for all cases, apps will run and look good on both platforms with minimal effort. This avoids the need to maintain two Pebble projects for one app.

In addition, as of Pebble SDK 3.6 there are macros that can be used to selectively include code in single statements. This is an alternative to the approach shown above using #ifdef:

window_set_background_color(s_main_window,
                            PBL_IF_COLOR_ELSE(GColorDukeBlue, GColorBlack));

See {% guide_link best-practices/building-for-every-pebble %} to learn more about these macros, as well as see a complete list.

PebbleKit Considerations

Apps that use PebbleKit Android will need to be re-compiled in Android Studio (or similar) with the PebbleKit Android 3.x (see {% guide_link communication/using-pebblekit-android %}) library in order to be compatible with the Pebble Time mobile application. No code changes are required, however.

PebbleKit iOS developers remain unaffected and their apps will continue to run with the new Pebble mobile application. However, iOS companion apps will need to be recompiled with PebbleKit iOS 3.x (see {% guide_link migration/pebblekit-ios-3 "PebbleKit iOS 3.0 Migration Guide" %}) to work with Pebble Time Round.

Changes to appinfo.json

There is a new field for tracking which version of the SDK the app is built for. For example, when using 3.x SDK add this line to the project's appinfo.json.

"sdkVersion": "3"

Apps will specify which hardware platforms they support (and wish to be built for) by declaring them in the targetPlatforms field of the project's appinfo.json file.

"targetPlatforms": [
  "aplite",
  "basalt",
  "chalk"
]

For each platform listed here, the SDK will generate an appropriate binary and resource pack that will be included in the .pbw file. This means that the app is actually compiled and resources are optimized once for each platform. The image below summarizes this build process:

build process

Note: If targetPlatforms is not specified in appinfo.json the app will be compiled for all platforms.

Apps can also elect to not appear in the app menu on the watch (if is is only pushing timeline pins, for example) by setting hiddenApp:

"watchapp": {
  "watchface": false,
  "hiddenApp": true
},

Project Resource Processing

SDK 3.x enhances the options for adding image resources to a Pebble project, including performing some pre-processing of images into compatible formats prior to bundling. For more details on the available resource types, check out the {% guide_link app-resources %} section of the guides.

Platform-specific Resources

Different Resources per Platform

It is possible to include different versions of resources on only one of the platforms with a specific type of display. Do this by appending ~bw or ~color to the name of the resource file and the SDK will prefer that file over another with the same name, but lacking the suffix.

This means is it possible to can include a smaller black and white version of an image by naming it example-image~bw.png, which will be included in the appropriate build over another file named example-image.png. In a similar manner, specify a resource for a color platform by appending ~color to the file name.

An example file structure is shown below.

my-project/
  resources/
    images/
      example-image~bw.png
      example-image~color.png
  src/
    main.c
  appinfo.json
  wscript

This resource will appear in appinfo.json as shown below.

"resources": {
  "media": [
    {
      "type": "bitmap",
      "name": "EXAMPLE_IMAGE",
      "file": "images/example-image.png"
    }
  ]
}

Read {% guide_link app-resources/platform-specific %} for more information about specifying resources per-platform.

Single-platform Resources

To only include a resource on a specific platform, add a targetPlatforms field to the resource's entry in the media array in appinfo.json. For example, the resource shown below will only be included for the Basalt build.

"resources": {
  "media": [
    {
      "type": "bitmap",
      "name": "BACKGROUND_IMAGE",
      "file": "images/background.png",
      "targetPlatforms": [
        "basalt"
      ]
    }
  ]
}

Changes to wscript

To support compilation for multiple hardware platforms and capabilities, the default wscript file included in every Pebble project has been updated.

If a project uses a customized wscript file and pebble convert-project is run (which will fully replace the file with a new compatible version), the wscript will be copied to wscript.backup.

View this GitHub gist to see a sample of what the new format looks like, and re-add any customizations afterwards.

Changes to Timezones

With SDK 2.x, all time-related SDK functions returned values in local time, with no concept of timezones. With SDK 3.x, the watch is aware of the user's timezone (specified in Settings), and will return values adjusted for this value.

API Changes Quick Reference

Compatibility Macros

Since SDK 3.0-dp2, pebble.h includes compatibility macros enabling developers to use the new APIs to access fields of opaque structures and still be compatible with both platforms. An example is shown below:

static GBitmap *s_bitmap;
// SDK 2.9
GRect bounds = s_bitmap->bounds;

// SDK 3.x
GRect bounds = gbitmap_get_bounds(s_bitmap);

Comparing Colors

Instead of comparing two GColor values directly, use the new gcolor_equal function to check for identical colors.

GColor a, b;

// SDK 2.x, bad
if (a == b) { }

// SDK 3.x, good
if (gcolor_equal(a, b)) { }

Note: Two colors with an alpha transparency(.a) component equal to 0 (completely transparent) are considered as equal.

Assigning Colors From Integers

Specify a color previously stored as an int and convert it to a GColor:

GColor a;

// SDK 2.x
a = (GColor)persist_read_int(key);

// SDK 3.x
a.argb = persist_read_int(key);

/* OR */

a = (GColor){.argb = persist_read_int(key)};

Specifying Black and White

The internal representation of SDK 2.x colors such as GColorBlack and GColorWhite have changed, but they can still be used with the same name.

PebbleKit JS Account Token

In SDK 3.0 the behavior of Pebble.getAccountToken() changes slightly. In previous versions, the token returned on Android could differ from that returned on iOS by dropping some zero characters. The table below shows the different tokens received for a single user across platforms and versions:

Platform Token
iOS 2.6.5 29f00dd7872ada4bd14b90e5d49568a8
iOS 3.x 29f00dd7872ada4bd14b90e5d49568a8
Android 2.3 29f0dd7872ada4bd14b90e5d49568a8
Android 3.x 29f00dd7872ada4bd14b90e5d49568a8

Note: This process should only be applied to new tokens obtained from Android platforms, to compare to tokens from older app versions.

To account for this difference, developers should adapt the new account token as shown below.

JavaScript

function newToOld(token) {
  return token.split('').map(function (x, i) {
    return (x !== '0' || i % 2 == 1) ? x : '';
  }).join('');
}

Python

def new_to_old(token):
    return ''.join(x for i, x in enumerate(token) if x != '0' or i % 2 == 1)

Ruby

def new_to_old(token)
  token.split('').select.with_index { |c, i| (c != '0' or i % 2 == 1) }.join('')
end

PHP

{% highlight { "language": "php", "options": { "startinline": true } } %} function newToOld($token) { $array = str_split($token); return implode('', array_map(function($char, $i) { return ($char !== '0' || $i % 2 == 1) ? $char : ''; }, $array, array_keys($array))); } {% endhighlight %}

Using the Status Bar

To help apps integrate aesthetically with the new system experience, all Windows are now fullscreen-only in SDK 3.x. To keep the time-telling functionality, developers should use the new StatusBarLayer API in their .load handler.

Note: Apps built with SDK 2.x will still keep the system status bar unless specified otherwise with window_set_fullscreen(window, true). As a result, such apps that have been recompiled will be shifted up sixteen pixels, and should account for this in any window layouts.

static StatusBarLayer *s_status_bar;
static void main_window_load(Window *window) {
  Layer *window_layer = window_get_root_layer(window);

  /* other UI code */

  // Set up the status bar last to ensure it is on top of other Layers
  s_status_bar = status_bar_layer_create();
  layer_add_child(window_layer, status_bar_layer_get_layer(s_status_bar));
}

By default, the status bar will look the same as it did on 2.x, minus the battery meter.

status-bar-default >{pebble-screenshot,pebble-screenshot--time-red}

To display the legacy battery meter on the Basalt platform, simply add an additional Layer after the StatusBarLayer, and use the following code in its LayerUpdateProc.

static void battery_proc(Layer *layer, GContext *ctx) {
  // Emulator battery meter on Aplite
  graphics_context_set_stroke_color(ctx, GColorWhite);
  graphics_draw_rect(ctx, GRect(126, 4, 14, 8));
  graphics_draw_line(ctx, GPoint(140, 6), GPoint(140, 9));

  BatteryChargeState state = battery_state_service_peek();
  int width = (int)(float)(((float)state.charge_percent / 100.0F) * 10.0F);
  graphics_context_set_fill_color(ctx, GColorWhite);
  graphics_fill_rect(ctx, GRect(128, 6, width, 4), 0, GCornerNone);
}
static void main_window_load(Window *window) {
  Layer *window_layer = window_get_root_layer(window);
  GRect bounds = layer_get_bounds(window_layer);

  /* other UI code */

  // Set up the status bar last to ensure it is on top of other Layers
  s_status_bar = status_bar_layer_create();
  layer_add_child(window_layer, status_bar_layer_get_layer(s_status_bar));

  // Show legacy battery meter
  s_battery_layer = layer_create(GRect(bounds.origin.x, bounds.origin.y, 
                                      bounds.size.w, STATUS_BAR_LAYER_HEIGHT));
  layer_set_update_proc(s_battery_layer, battery_proc);
  layer_add_child(window_layer, s_battery_layer);
}

Note: To update the battery meter more frequently, use layer_mark_dirty() in a BatteryStateService subscription. Unless the current Window is long-running, this should not be neccessary.

The StatusBarLayer can also be extended by the developer in similar ways to the above. The API also allows setting the layer's separator mode and foreground/background colors:

status_bar_layer_set_separator_mode(s_status_bar, 
                                            StatusBarLayerSeparatorModeDotted);
status_bar_layer_set_colors(s_status_bar, GColorClear, GColorWhite);

This results in a a look that is much easier to integrate into a color app.

status-bar-color >{pebble-screenshot,pebble-screenshot--time-red}

Using PropertyAnimation

The internal structure of PropertyAnimation has changed, but it is still possible to access the underlying Animation:

// SDK 2.x
Animation *animation = &prop_animation->animation;
animation = (Animation*)prop_animation;

// SDK 3.x
Animation *animation = property_animation_get_animation(prop_animation);
animation = (Animation*)prop_animation;

Accessing internal fields of PropertyAnimation has also changed. For example, to access the GPoint in the from member of an animation:

GPoint p;
PropertyAnimation *prop_anim;

// SDK 2.x
prop_animation->values.from.gpoint = p;

// SDK 3.x
property_animation_set_from_gpoint(prop_anim, &p);

Animations are now automatically freed when they have finished. This means that code using animation_destroy() should be corrected to no longer do this manually when building with SDK 3.x, which will fail. SDK 2.x code must still manually free Animations as before.

Developers can now create complex synchronized and chained animations using the new features of the Animation Framework. Read {% guide_link graphics-and-animations/animations %} to learn more.

Accessing GBitmap Members

GBitmap is now opaque, so accessing structure members directly is no longer possible. However, direct references to members can be obtained with the new accessor functions provided by SDK 3.x:

static GBitmap *s_bitmap = gbitmap_create_with_resource(RESOURCE_ID_EXAMPLE_IMAGE);

// SDK 2.x
GRect image_bounds = s_bitmap->bounds;

// SDK 3.x
GRect image_bounds = gbitmap_get_bounds(s_bitmap);

Drawing Rotated Bitmaps

{% markdown %} The bitmap rotation API requires a significant amount of CPU power and will have a substantial effect on users' battery life.

There will also be a large reduction in performance of the app and a lower framerate may be seen. Use alternative drawing methods such as Draw Commands or GPaths wherever possible. {% endmarkdown %%}

Alternatively, draw a GBitmap with a rotation angle and center point inside a LayerUpdateProc using graphics_draw_rotated_bitmap().

Using InverterLayer

SDK 3.x deprecates the InverterLayer UI component which was primarily used for MenuLayer highlighting. Developers can now make use of menu_cell_layer_is_highlighted() inside a MenuLayerDrawRowCallback to determine which text and selection highlighting colors they prefer.

Using this for determining highlight behaviour is preferable to using menu_layer_get_selected_index(). Row drawing callbacks may be invoked multiple times with a different highlight status on the same cell in order to handle partially highlighted cells during animation.