mirror of
https://github.com/google/pebble.git
synced 2025-03-19 18:41:21 +00:00
528 lines
16 KiB
Markdown
528 lines
16 KiB
Markdown
|
---
|
||
|
# Copyright 2025 Google LLC
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
|
||
|
title: SDK 3.x Migration Guide
|
||
|
description: Migrating Pebble apps from SDK 2.x to SDK 3.x.
|
||
|
permalink: /guides/migration/migration-guide-3/
|
||
|
generate_toc: true
|
||
|
guide_group: migration
|
||
|
order: 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:
|
||
|
|
||
|
```c
|
||
|
#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`:
|
||
|
|
||
|
```c
|
||
|
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:
|
||
|
|
||
|

|
||
|
|
||
|
> 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.
|
||
|
|
||
|
```text
|
||
|
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](https://gist.github.com/pebble-gists/72a1a7c85980816e7f9b)
|
||
|
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:
|
||
|
|
||
|
```c
|
||
|
static GBitmap *s_bitmap;
|
||
|
```
|
||
|
|
||
|
```c
|
||
|
// 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.
|
||
|
|
||
|
```c
|
||
|
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`:
|
||
|
|
||
|
```c
|
||
|
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**
|
||
|
|
||
|
```js
|
||
|
function newToOld(token) {
|
||
|
return token.split('').map(function (x, i) {
|
||
|
return (x !== '0' || i % 2 == 1) ? x : '';
|
||
|
}).join('');
|
||
|
}
|
||
|
```
|
||
|
|
||
|
**Python**
|
||
|
|
||
|
```python
|
||
|
def new_to_old(token):
|
||
|
return ''.join(x for i, x in enumerate(token) if x != '0' or i % 2 == 1)
|
||
|
```
|
||
|
|
||
|
**Ruby**
|
||
|
|
||
|
```ruby
|
||
|
def new_to_old(token)
|
||
|
token.split('').select.with_index { |c, i| (c != '0' or i % 2 == 1) }.join('')
|
||
|
end
|
||
|
```
|
||
|
|
||
|
**PHP**
|
||
|
|
||
|
<div>
|
||
|
{% 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 %}
|
||
|
</div>
|
||
|
|
||
|
|
||
|
### Using the Status Bar
|
||
|
|
||
|
To help apps integrate aesthetically with the new system experience, all
|
||
|
``Window``s 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.
|
||
|
|
||
|
```c
|
||
|
static StatusBarLayer *s_status_bar;
|
||
|
```
|
||
|
|
||
|
```c
|
||
|
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.
|
||
|
|
||
|

|
||
|
|
||
|
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``.
|
||
|
|
||
|
```c
|
||
|
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);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
```c
|
||
|
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:
|
||
|
|
||
|
```c
|
||
|
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.
|
||
|
|
||
|

|
||
|
|
||
|
|
||
|
### Using PropertyAnimation
|
||
|
|
||
|
The internal structure of ``PropertyAnimation`` has changed, but it is still
|
||
|
possible to access the underlying ``Animation``:
|
||
|
|
||
|
```c
|
||
|
// 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:
|
||
|
|
||
|
```c
|
||
|
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:
|
||
|
|
||
|
```c
|
||
|
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
|
||
|
|
||
|
<div class="alert alert--fg-white alert--bg-dark-red">
|
||
|
{% 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`](``GPath``) wherever possible.
|
||
|
{% endmarkdown %%}
|
||
|
</div>
|
||
|
|
||
|
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.
|