13 KiB
title | description | guide_group | order |
---|---|---|---|
Building for Every Pebble | How to write one app compatible with all Pebble smartwatches. | best-practices | 0 |
The difference in capabilities between the various Pebble hardware platforms are listed in {% guide_link tools-and-resources/hardware-information %}. For example, the Basalt, Chalk and Emery platforms support 64 colors, whereas the Aplite and Diorite platforms only support two colors. This can make developing apps with rich color layouts difficult when considering compatibility with other non-color hardware. Another example is using platform specific APIs such as Health or Dictation.
To make life simple for users, developers should strive to write one app that can be used on all platforms. To help make this task simpler for developers, the Pebble SDK provides numerous methods to accommodate different hardware capabilities in code.
Preprocessor Directives
It is possible to specify certain blocks of code to be compiled for specific
purposes by using the #ifdef
preprocessor statement. For example, the
Dictation
API should be excluded on platforms with no microphone:
#if defined(PBL_MICROPHONE)
// Start dictation UI
dictation_session_start(s_dictation_session);
#else
// Microphone is not available
text_layer_set_text(s_some_layer, "Dictation not available!");
#endif
When designing UI layouts, any use of colors on compatible platforms can be
adapted to either black or white on non-color platforms. The PBL_COLOR
and
PBL_BW
symbols will be defined at compile time when appropriate capabilities
are available:
#if defined(PBL_COLOR)
text_layer_set_text_color(s_text_layer, GColorRed);
text_layer_set_background_color(s_text_layer, GColorChromeYellow);
#else
text_layer_set_text_color(s_text_layer, GColorWhite);
text_layer_set_background_color(s_text_layer, GColorBlack);
#endif
This is useful for blocks of multiple statements that change depending on the
availability of color support. For single statements, this can also be achieved
by using the PBL_IF_COLOR_ELSE()
macro.
window_set_background_color(s_main_window, PBL_IF_COLOR_ELSE(GColorJaegerGreen, GColorBlack));
See below for a complete list of defines and macros available.
Available Defines and Macros
The tables below show a complete summary of all the defines and associated macros available to conditionally compile or omit feature-dependant code. The macros are well-suited for individual value selection, whereas the defines are better used to select an entire block of code.
Define | MACRO | Available |
---|---|---|
PBL_BW |
PBL_IF_BW_ELSE() |
Running on hardware that supports only black and white. |
PBL_COLOR |
PBL_IF_COLOR_ELSE() |
Running on hardware that supports 64 colors. |
PBL_MICROPHONE |
PBL_IF_MICROPHONE_ELSE() |
Running on hardware that includes a microphone. |
PBL_COMPASS |
None | Running on hardware that includes a compass. |
PBL_SMARTSTRAP |
PBL_IF_SMARTSTRAP_ELSE() |
Running on hardware that includes a smartstrap connector, but does not indicate that the connector is capable of supplying power. |
PBL_SMARTSTRAP_POWER |
None | Running on hardware that includes a smartstrap connector capable of supplying power. |
PBL_HEALTH |
PBL_IF_HEALTH_ELSE() |
Running on hardware that supports Pebble Health and the HealthService API. |
PBL_RECT |
PBL_IF_RECT_ELSE() |
Running on hardware with a rectangular display. |
PBL_ROUND |
PBL_IF_ROUND_ELSE() |
Running on hardware with a round display. |
PBL_DISPLAY_WIDTH |
None | Determine the screen width in pixels. |
PBL_DISPLAY_HEIGHT |
None | Determine the screen height in pixels. |
PBL_PLATFORM_APLITE |
None | Built for Pebble/Pebble Steel. |
PBL_PLATFORM_BASALT |
None | Built for Pebble Time/Pebble Time Steel. |
PBL_PLATFORM_CHALK |
None | Built for Pebble Time Round. |
PBL_PLATFORM_DIORITE |
None | Built for Pebble 2. |
PBL_PLATFORM_EMERY |
None | Built for Pebble Time 2. |
PBL_SDK_2 |
None | Compiling with SDK 2.x (deprecated). |
PBL_SDK_3 |
None | Compiling with SDK 3.x. or 4.x. |
Note: It is strongly recommended to conditionally compile code using applicable feature defines instead of
PBL_PLATFORM
defines to be as specific as possible.
API Detection
In addition to platform and capabilities detection, we now provide API detection to detect if a specific API method is available. This approach could be considered future-proof, since platforms and capabilities may come and go. Let's take a look at a simple example:
#if PBL_API_EXISTS(health_service_peek_current_value)
// Do something if specific Health API exists
#endif
Avoid Hardcoded Layout Values
With the multiple display shapes and resolutions available, developers should try and avoid hardcoding layout values. Consider the example below:
static void window_load(Window *window) {
// Create a full-screen Layer - BAD
s_some_layer = layer_create(GRect(0, 0, 144, 168));
}
The hardcoded width and height of this layer will cover the entire screen on
Aplite, Basalt and Diorite, but not on Chalk or Emery. This kind of screen
size-dependant calculation should use the UnobstructedArea
bounds of the
Window
itself:
static void window_load(Window *window) {
// Get the unobstructed bounds of the Window
Layer window_layer = window_get_root_layer(window);
GRect window_bounds = layer_get_unobstructed_bounds(window_layer);
// Properly create a full-screen Layer - GOOD
s_some_layer = layer_create(window_bounds);
}
Another common use of this sort of construction is to make a Layer
that is
half the unobstructed screen height. This can also be correctly achieved using
the Window
unobstructed bounds:
GRect layer_bounds = window_bounds;
layer_bounds.size.h /= 2;
// Create a Layer that is half the screen height
s_some_layer = layer_create(layer_bounds);
This approach is also advantageous in simplifying updating an app for a future
new screen size, as proportional layout values will adapt as appropriate when
the Window
unobstructed bounds change.
Screen Sizes
To ease the introduction of the Emery platform, the Pebble SDK introduced new compiler directives to allow developers to determine the screen width and height. This is preferable to using platform detection, since multiple platforms share the same screen width and height.
#if PBL_DISPLAY_HEIGHT == 228
uint8_t offset_y = 100;
#elif PBL_DISPLAY_HEIGHT == 180
uint8_t offset_y = 80;
#else
uint8_t offset_y = 60;
#endif
Note: Although this method is preferable to platform detection, it is better to dynamically calculate the display width and height based on the unobstructed bounds of the root layer.
Pebble C WatchInfo
The WatchInfo
API can be used to determine exactly which Pebble model and
color an app is running on. Apps can use this information to dynamically
modify their layout or behavior depending on which Pebble the user is wearing.
For example, the display on Pebble Steel is located at a different vertical
position relative to the buttons than on Pebble Time. Any on-screen button hints
can be adjusted to compensate for this using WatchInfoModel
.
static void window_load(Window *window) {
Layer window_layer = window_get_root_layer(window);
GRect window_bounds = layer_get_bounds(window_layer);
int button_height, y_offset;
// Conditionally set layout parameters
switch(watch_info_get_model()) {
case WATCH_INFO_MODEL_PEBBLE_STEEL:
y_offset = 64;
button_height = 44;
break;
case WATCH_INFO_MODEL_PEBBLE_TIME:
y_offset = 58;
button_height = 56;
break;
/* Other cases */
default:
y_offset = 0;
button_height = 0;
break;
}
// Set the Layer frame
GRect layer_frame = GRect(0, y_offset, window_bounds.size.w, button_height);
// Create the Layer
s_label_layer = text_layer_create(layer_frame);
layer_add_child(window_layer, text_layer_get_layer(s_label_layer));
/* Other UI code */
}
Developers can also use WatchInfoColor
values to theme an app for each
available color of Pebble.
static void window_load(Window *window) {
GColor text_color, background_color;
// Choose different theme colors per watch color
switch(watch_info_get_color()) {
case WATCH_INFO_COLOR_RED:
// Red theme
text_color = GColorWhite;
background_color = GColorRed;
break;
case WATCH_INFO_COLOR_BLUE:
// Blue theme
text_color = GColorBlack;
background_color = GColorVeryLightBlue;
break;
/* Other cases */
default:
text_color = GColorBlack;
background_color = GColorWhite;
break;
}
// Use the conditionally set value
text_layer_set_text_color(s_label_layer, text_color);
text_layer_set_background_color(s_label_layer, background_color);
/* Other UI code */
}
PebbleKit JS Watch Info
Similar to Pebble C WatchInfo above, the PebbleKit JS
Pebble.getActiveWatchInfo()
method allows developers to determine
which model and color of Pebble the user is wearing, as well as the firmware
version running on it. For example, to obtain the model of the watch:
Note: See the section below to avoid problem using this function on older app version.
// Get the watch info
var info = Pebble.getActiveWatchInfo();
console.log('Pebble model: ' + info.model);
Detecting Platform-specific JS Features
A number of features in PebbleKit JS (such as Pebble.timelineSubscribe()
and
Pebble.getActiveWatchInfo()
) exist on SDK 3.x. If an app tries to use any of
these on an older Pebble mobile app version where they are not available, the JS
app will crash.
To prevent this, be sure to check for the availability of the function before
calling it. For example, in the case of Pebble.getActiveWatchInfo()
:
if (Pebble.getActiveWatchInfo) {
// Available.
var info = Pebble.getActiveWatchInfo();
console.log('Pebble model: ' + info.model);
} else {
// Gracefully handle no info available
}
Platform-specific Resources
With the availability of color support on Basalt, Chalk and Emery, developers may wish to include color versions of resources that had previously been pre-processed for Pebble's black and white display. Including both versions of the resource is expensive from a resource storage perspective, and lays the burden of packing redundant color resources in an Aplite or Diorite app when built for multiple platforms.
To solve this problem, the Pebble SDK allows developers to specify which version
of an image resource is to be used for each display type, using ~bw
or
~color
appended to a file name. Resources can also be bundled only with
specific platforms using the targetPlatforms
property for each resource.
For more details about packaging resources specific to each platform, as well as
more tags available similar to ~color
, read
{% guide_link app-resources/platform-specific %}.
Multiple Display Shapes
With the introduction of the Chalk platform, a new round display type is available with increased pixel resolution. To distinguish between the two possible shapes of display, developers can use defines to conditionally include code segments:
#if defined(PBL_RECT)
printf("This is a rectangular display!");
#elif defined(PBL_ROUND)
printf("This is a round display!");
#endif
Another approach to this conditional compilation is to use the
PBL_IF_RECT_ELSE()
and PBL_IF_ROUND_ELSE()
macros, allowing values to be
inserted into expressions that might otherwise require a set of #define
statements similar to the previous example. This would result in needless
verbosity of four extra lines of code when only one is actually needed. These
are used in the following manner:
// Conditionally print out the shape of the display
printf("This is a %s display!", PBL_IF_RECT_ELSE("rectangular", "round"));
This mechanism is best used with window bounds-derived layout size and position
value. See the Avoid Hardcoded Layout Values
section above for more information. Making good use of the builtin Layer
types will also help safeguard apps against display shape and size changes.
Another thing to consider is rendering text on a round display. Due to the rounded corners, each horizontal line of text will have a different available width, depending on its vertical position.