Devoxx 2015 presentation
This presentation will quickly go over the 3 main smartwatch platforms on the market, namely Pebble, Android Wear and Apple Watch. We will explain the characteristics of each platform, the usage scenarios it supports, the architecture of an app, show the language and the tools that are available to develop apps, describe the development lifecycle and illustrate with code samples (no live-coding). We will demonstrate all that based on a common app developed for the three platforms.
2. #Devoxx #smartvoxx @sarbogast @eloudsa
• Who owns a smartwatch?
• Who is an Android developer?
• Who is an iOS developer?
• Who is a Pebble developer?
• Who is a Rolex developer?
• Who has already written a smartwatch app?
• Who is a member of the Night’s Watch?
Survey
3. #Devoxx #smartvoxx @sarbogast @eloudsa
Sébastien Arbogast
@sarbogast
• Java developer for 10 years
• iOS developers for 5 years (developer of the first Devoxx schedule
app)
• Pebble developer for 2 years
• Owner of TikTok Lunatik with iPod Nano
• VP of engineering for Take Eat Easy
4. #Devoxx #smartvoxx @sarbogast @eloudsa
Said Eloudrhiri
@eloudsa
• Developer since 1992
• Agile Coach and trainer
• Devoxx4Kids helper (Sphero, MindStorms, CodeCombat)
• Side Projects: mobile development
• Husband and father of Nora, Rayane and Djenna
5. #Devoxx #smartvoxx @sarbogast @eloudsa
Disclaimer
We are not related to Google,Apple or Pebble.
We are just curious developers sharing our experience.
Materials used in this presentation remains the property of their
owners.
Any questions?
22. #Devoxx #smartvoxx @sarbogast @eloudsa
Apple Watch
• Select the Watch App scheme
• Select your emulator configuration
• Run
• You can debug both emulators at the same time
23. #Devoxx #smartvoxx @sarbogast @eloudsa
Android
• Install Android Wear on your Phone
• Create Wear emulator
• Run Wear emulator
• Open ports to let emulator communicate with physical phone
• Pair the Phone and the Wear Emulator
• Deploy application on Wear and/or Mobile
24. #Devoxx #smartvoxx @sarbogast @eloudsa
Pebble
• Connect Pebble app with CloudPebble
• Enable Developer Connections
• Run in CloudPebble
30. #Devoxx #smartvoxx @sarbogast @eloudsa
Compatibility
• Android Wear:Android phones + iPhone (with some
limitations)
• Apple Watch: iPhones only
• Pebble:Android phones + iPhones (with SDKs to integrate
with apps on both platforms)
36. #Devoxx #smartvoxx @sarbogast @eloudsa
Human Interface Guidelines
• Lightweight interactions
• Holistic design
• Personal communication
37. #Devoxx #smartvoxx @sarbogast @eloudsa
Creative vision for Android Wear
• Suggest: Context Stream
• Demand: Cue Cards
• Glanceable
• Zero or Low interaction
Beam me up!
38. #Devoxx #smartvoxx @sarbogast @eloudsa
Guidelines and patterns
• Cards
• List options with Menus
• Execute actions with action bars
• Prompting User Action on the Phone
• Show Time and Data with the Status Bar
• Show Alerts and Get Decisions with Modal
Windows
• UsingVibrations and Haptic Feedback
• Handling Connection Problems
41. #Devoxx #smartvoxx @sarbogast @eloudsa
Android Wear
• Full-screen apps
• Notifications
• Custom notifications
• Watch face
4
“If you don't have a Rolex by the time you
reach 50,
then you have clearly failed in your life.”
Jacques Séguéla
43. #Devoxx #smartvoxx @sarbogast @eloudsa
Languages
• Apple Watch: Swift or Objective-C
• Android Wear: Java (or Kotlin or Groovy or …)
• Pebble: C or Javascript (Pebble.js)
44. #Devoxx #smartvoxx @sarbogast @eloudsa
self.table.setNumberOfRows(self.schedules!.count, withRowType: "schedule")
for (index,schedule) in self.schedules!.enumerate() {
if let row = self.table.rowControllerAtIndex(index)
as? ScheduleRowController {
row.label.setText(
schedule.title!.stringByReplacingOccurrencesOfString(
NSLocalizedString("Schedule for ", comment:""),
withString: ""
)
)
}
}
Apple Watch
51. #Devoxx #smartvoxx @sarbogast @eloudsa
static void main_window_load(Window *window) {
// Get information about the Window
Layer *window_layer = window_get_root_layer(window);
GRect bounds = layer_get_bounds(window_layer);
// Create the TextLayer with specific bounds
s_time_layer = text_layer_create(
GRect(0, PBL_IF_ROUND_ELSE(58, 52), bounds.size.w, 50));
// Improve the layout to be more like a watchface
text_layer_set_background_color(s_time_layer, GColorClear);
text_layer_set_text_color(s_time_layer, GColorBlack);
text_layer_set_text(s_time_layer, "00:00");
text_layer_set_font(s_time_layer, fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD));
text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter);
// Add it as a child layer to the Window's root layer
layer_add_child(window_layer, text_layer_get_layer(s_time_layer));
}
Pebble
61. #Devoxx #smartvoxx @sarbogast @eloudsa
function loadSiteList(accessToken, refreshToken) {
console.log("Loading site list for access token " + accessToken);
var response;
var req = new XMLHttpRequest();
// build the GET request
req.open('GET', "https://api.myfox.me:443/v2/client/site/items?access_token=" + accessToken, true);
req.onload = function(e) {
if (req.readyState == 4) {
// 200 - HTTP OK
if(req.status == 200) {
console.log(req.responseText);
response = JSON.parse(req.responseText);
var siteList;
if (response.status === 'OK') {
siteList = response.payload.items;
var msg = {};
msg.messageType = MessageType.SITE_LIST;
for(var i = 0; i < siteList.length; i++){
var site = siteList[i];
msg['' + site.siteId] = site.label;
}
console.log("Sending response back to Pebble: " + JSON.stringify(msg));
Pebble.sendAppMessage(msg);
} else {
console.log("Status not OK");
Pebble.sendAppMessage({messageType:MessageType.ERROR, errorMessage:"Could not load list of sites. Please try again later."});
}
} else if(req.status == 401 && refreshToken){
getNewAccessToken(refreshToken, loadSiteList);
} else {
console.log("Request returned error code " + req.status.toString());
Pebble.sendAppMessage({messageType:MessageType.ERROR});
}
}
};
req.send(null);
}
Phone to internet and back
63. #Devoxx #smartvoxx @sarbogast @eloudsa
Watch Connectivity
Apple
Watch
update application context
send message
transfer user info
transfer file
replace
live
queue
big data
64. #Devoxx #smartvoxx @sarbogast @eloudsa
if WCSession.isSupported() {
session = WCSession.defaultSession()
session?.delegate = self
session?.activateSession()
if let session = self.session where session.reachable {
session.sendMessage(["talkSlot" : talkSlotMessage as NSDictionary],
replyHandler: { (reply:[String : AnyObject]) -> Void in
},
errorHandler: { (error:NSError) -> Void in
print(error)
}
)
} else {
session?.transferUserInfo(["talkSlot" : talkSlotMessage as NSDictionary])
}
}
Watch Connectivity
65. #Devoxx #smartvoxx @sarbogast @eloudsa
Google API Client
Device
Google Play
Services
Your App
Google API Client
Google Play
services library
Message API
Data API
Node API
66. #Devoxx #smartvoxx @sarbogast @eloudsa
public class ScheduleActivity extends Activity {
// Google Play Services
private GoogleApiClient mApiClient;
…
@Override
protected void onStart() {
super.onStart();
mApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.build();
mApiClient.connect();
}
@Override
protected void onStop() {
if ((mApiClient != null) && (mApiClient.isConnected())) {
Wearable.DataApi.removeListener(mApiClient, this);
mApiClient.disconnect();
}
super.onStop();
}
Google API Client
1
2
3
68. #Devoxx #smartvoxx @sarbogast @eloudsa
public class ScheduleActivity extends Activity {
…
private void sendMessage(final String path, final String message) {
new Thread(new Runnable() {
@Override
public void run() {
// broadcast the message to all connected devices
final NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(mApiClient).await();
for (Node node : nodes.getNodes()) {
Wearable.MessageApi.sendMessage(mApiClient, node.getId(), path, message.getBytes()).await();
}
}
}).start();
}
Message API: Send Message
69. #Devoxx #smartvoxx @sarbogast @eloudsa
public class WearService extends WearableListenerService {
…
@Override
public void onMessageReceived(MessageEvent messageEvent) {
// Processing the incoming message
String path = messageEvent.getPath();
String data = new String(messageEvent.getData());
if (path.equalsIgnoreCase(Constants.SCHEDULES_PATH)) {
retrieveSchedules();
return;
}
…
Message API: Message Received
70. #Devoxx #smartvoxx @sarbogast @eloudsa
Data Synchronisation
Phone Watch
Node Node
Data API.putDataItem()
Wearable.DataApi
onDataChanged()
DataApi.DataListener
71. #Devoxx #smartvoxx @sarbogast @eloudsa
// send Schedules to the watch
private void sendSchedules(List<Link> schedules) {
final PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(Constants.SCHEDULES_PATH);
ArrayList<DataMap> schedulesDataMap = new ArrayList<>();
// process each schedule
for (Link schedule : schedules) {
DataMap scheduleDataMap = new DataMap();
// process and push schedule's data
scheduleDataMap.putString("day", Utils.getLastPartUrl(schedule.getHref()));
scheduleDataMap.putString("title", schedule.getTitle());
schedulesDataMap.add(scheduleDataMap);
}
// store the list in the datamap to send it to the watch
putDataMapRequest.getDataMap().putDataMapArrayList(Constants.LIST_PATH, schedulesDataMap);
// send the schedules
if (mApiClient.isConnected()) {
Wearable.DataApi.putDataItem(mApiClient, putDataMapRequest.asPutDataRequest());
}
}
Data API: Send Data
1
2
3
4
72. #Devoxx #smartvoxx @sarbogast @eloudsa
@Override
public void onDataChanged(DataEventBuffer dataEventBuffer) {
for (DataEvent event : dataEventBuffer) {
// Check if list of schedules has changed
if (event.getType() == DataEvent.TYPE_CHANGED && event.getDataItem().getUri().getPath().startsWith(Constants.SCHEDULES_PATH)) {
// get the list of schedules from the incoming data event
SchedulesListWrapper schedulesListWrapper = new SchedulesListWrapper();
final List<Schedule> schedulesList = schedulesListWrapper.getSchedulesList(event);
runOnUiThread(new Runnable() {
@Override
public void run() {
// hide the progress bar
findViewById(R.id.progressBar).setVisibility(View.GONE);
mListViewAdapter.refresh(schedulesList);
}
});
return;
}
}
}
1
2
3
4
Data API: Data changed
79. #Devoxx #smartvoxx @sarbogast @eloudsa
Apple Watch
• Package the Apple Watch app with the iPhone app
• Release the iPhone app like any other
• Wait for review…
• Wait again…
• Wait some more…
81. #Devoxx #smartvoxx @sarbogast @eloudsa
Prepare the build
• Include permissions required by Wear into Phone (Manifest)
• Use same package name and version number (build.gradle)
89. #Devoxx #smartvoxx @sarbogast @eloudsa
Summary
• Huge inequalities in terms of development platform ease-of-
use
• Apple obviously took time to add abstraction layers that make
development more expressive
• Short learning curve on Android Wear compared to Apple
Watch
• Tooling support not up-to-date on Android
• Documentation is not really finished for both platforms
• Not all apps make sense on smartwatches
90. #Devoxx #smartvoxx @sarbogast @eloudsa
Apps that work on smartwatches
• countdowns and timers
• status checks: what’s the temperature? what’s my next
session? what’s the score of the game?
• remote controls: switch off the light, change the music, open
my hotel room, pay for my shopping
• notification responders: invitation to a meeting -> what’s the
meeting about, somebody sent me a message -> what does it
say?
• data trackers: where am I? how many calories am I burning?
what’s my speed?)
91. #Devoxx #smartvoxx @sarbogast @eloudsa
Apps that don’t make sense
• games of any kind
• any long reading (news, books, etc.)
• ecommerce
• video or image viewing
• anything that requires text input