Smartwatch Landscape

93
@sarbogast @eloudsa #Devoxx #smartvoxx Smartwatch Landscape Sébastien Arbogast Said Eloudrhiri

Transcript of Smartwatch Landscape

@sarbogast @eloudsa#Devoxx #smartvoxx

Smartwatch LandscapeSébastien Arbogast

Said Eloudrhiri

#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

#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

#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

#Devoxx #smartvoxx @sarbogast @eloudsa

DisclaimerWe 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?

#Devoxx #smartvoxx @sarbogast @eloudsa

Why develop for smartwatches?

#Devoxx #smartvoxx @sarbogast @eloudsa

Glanceable

#Devoxx #smartvoxx @sarbogast @eloudsa

Sensors

#Devoxx #smartvoxx @sarbogast @eloudsa

Internet of Things

#Devoxx #smartvoxx @sarbogast @eloudsa

Notification-driven

#Devoxx #smartvoxx @sarbogast @eloudsa

Small screen

#Devoxx #smartvoxx @sarbogast @eloudsa

Interactions

#Devoxx #smartvoxx @sarbogast @eloudsa

Personal use

#Devoxx #smartvoxx @sarbogast @eloudsa

Landscape

Apple Watch

Android Wear

Pebble Tizen

#Devoxx #smartvoxx @sarbogast @eloudsa

IDE

#Devoxx #smartvoxx @sarbogast @eloudsa

XCode

#Devoxx #smartvoxx @sarbogast @eloudsa

Android Studio

#Devoxx #smartvoxx @sarbogast @eloudsa

Debugging with Emulator

USB

Bridge

adb -d forward tcp:5601 tcp:5601

#Devoxx #smartvoxx @sarbogast @eloudsa

Android Studio or Eclipse?

• Android Development Tools (ADT)

• Andmore - Eclipse Android Tooling (Incubation Project)https://projects.eclipse.org/projects/tools.andmore

http://developer.android.com/tools/sdk/eclipse-adt.html

#Devoxx #smartvoxx @sarbogast @eloudsa

CloudPebble

#Devoxx #smartvoxx @sarbogast @eloudsa

Development cycle

#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

#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

#Devoxx #smartvoxx @sarbogast @eloudsa

Pebble

• Connect Pebble app with CloudPebble

• Enable Developer Connections

• Run in CloudPebble

#Devoxx #smartvoxx @sarbogast @eloudsa

Available Watches

#Devoxx #smartvoxx @sarbogast @eloudsa

Apple Watch

272x340312x390

#Devoxx #smartvoxx @sarbogast @eloudsa

Android Wear

#Devoxx #smartvoxx @sarbogast @eloudsa

Square Round Round Chin

Form factors

#Devoxx #smartvoxx @sarbogast @eloudsa

Pebble

Classic Steel Time Time Steel Time Round144x168

#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)

#Devoxx #smartvoxx @sarbogast @eloudsa

Inputs and Outputs

#Devoxx #smartvoxx @sarbogast @eloudsa

Apple Watch• Gestures

• Force Touch

• Digital Crown

• Side Button

• Taptic Engine

• Microphone and speaker

• Accelerometer

• Heart rate monitor

• (NFC)

#Devoxx #smartvoxx @sarbogast @eloudsa

• Touch screen

• Shake moves

• Button

• Ambiant mode

• Microphone (Voice control)

• Heart rate monitor (PPG)

• Gyro, Accelerometer, Compass

• Barometer

• NFC

• GPS

Android Wear

#Devoxx #smartvoxx @sarbogast @eloudsa

Pebble

• Non-touch (color) e-ink screen

• 4 buttons

• Vibration

• Microphone

#Devoxx #smartvoxx @sarbogast @eloudsa

Design Principles

#Devoxx #smartvoxx @sarbogast @eloudsa

Human Interface Guidelines

• Lightweight interactions

• Holistic design

• Personal communication

#Devoxx #smartvoxx @sarbogast @eloudsa

Creative vision for Android Wear

• Suggest: Context Stream

• Demand: Cue Cards

• Glanceable

• Zero or Low interactionBeam me up!

#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

• Using Vibrations and Haptic Feedback

• Handling Connection Problems

#Devoxx #smartvoxx @sarbogast @eloudsa

Different kinds of apps

#Devoxx #smartvoxx @sarbogast @eloudsa

Apple Watch

• Apps

• Notifications

• Glances

• Complications

#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

#Devoxx #smartvoxx @sarbogast @eloudsa

Pebble

• Apps

• Timeline integration

#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)

#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

Demo

#Devoxx #smartvoxx @sarbogast @eloudsa

#Devoxx #smartvoxx @sarbogast @eloudsa

Layout: Round / Square

#Devoxx #smartvoxx @sarbogast @eloudsa

<?xml version="1.0" encoding="utf-8"?> <android.support.wearable.view.WatchViewStub xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/watch_talk_stub" android:layout_width="match_parent" android:layout_height="match_parent" app:rectLayout="@layout/talk_rect_fragment" app:roundLayout="@layout/talk_round_fragment" tools:context=".TalkActivity" tools:deviceIds="wear"> </android.support.wearable.view.WatchViewStub>

Layout: Mainres/layout/talk_fragment.xml

#Devoxx #smartvoxx @sarbogast @eloudsa

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:paddingTop="15dp"> <TextView android:id="@+id/title" android:layout_width="fill_parent" android:layout_height="wrap_content"

Sub-layout: Squareres/layout/talk_rect_fragment.xml

#Devoxx #smartvoxx @sarbogast @eloudsa

<?xml version="1.0" encoding="utf-8"?><android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="15dp"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/title" android:layout_width="fill_parent" android:layout_height="wrap_content"

Sub-layout: Roundres/layout/talk_round_fragment.xml

#Devoxx #smartvoxx @sarbogast @eloudsa

CloudPebble UI editor

#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

#Devoxx #smartvoxx @sarbogast @eloudsa

Internet connectivity

#Devoxx #smartvoxx @sarbogast @eloudsa

Apple Watch (NSURLSession)

Apple Watch

Apple Watch app

Internet

#Devoxx #smartvoxx @sarbogast @eloudsa

func loadSchedulesForConference(conference:String, callback: ([Schedule]) -> (Void)) { //Configure session and disable caching as it fails let configuration = NSURLSessionConfiguration.defaultSessionConfiguration() configuration.requestCachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData let session = NSURLSession(configuration: configuration) //Call API let schedulesURL = NSURL(string: "http://cfp.devoxx.be/api/conferences/\(conference)/schedules/")! let task = session.dataTaskWithURL(schedulesURL) { (data: NSData?, response:NSURLResponse?, error:NSError?) -> Void in //process data } task.resume() }

Apple Watch

#Devoxx #smartvoxx @sarbogast @eloudsa

Internet connectivity

Fallback solution when Bluetooth not available.

Unable to connect on remote servers.

#Devoxx #smartvoxx @sarbogast @eloudsa

/** * Created by eloudsa on 30/10/15. */public interface DevoxxApi { @GET("/conferences/{conference}/schedules") void getSchedules(@Path("conference") String conference, Callback<Schedules> callback); @GET("/conferences/{conference}/schedules/{day}") void getSchedule(@Path("conference") String conference, @Path("day") String day, Callback<SlotList> callback); @GET("/conferences/{conference}/talks/{talkid}") void getTalk(@Path("conference") String conference, @Path("talkid") String uuid, Callback<Talk> callback); @GET("/conferences/{conference}/speakers/{uuid}") void getSpeaker(@Path("conference") String conference, @Path("uuid") String uuid, Callback<Speaker> callback);}

Android Wear: EndpointsRetrofit REST API client from Square

#Devoxx #smartvoxx @sarbogast @eloudsa

@Overridepublic void onCreate() { super.onCreate();… // prepare the REST build mRestAdapter = new RestAdapter.Builder() .setEndpoint(getResources().getString(R.string.devoxx_rest_api)) .build(); mMethods = mRestAdapter.create(DevoxxApi.class); mConferenceName = getResources().getString(R.string.devoxx_conference); }

Android Wear: Setup

#Devoxx #smartvoxx @sarbogast @eloudsa

// Retrieve schedules from Devoxxprivate void retrieveSchedules() { // retrieve the schedules list from the server Callback callback = new Callback() { @Override public void success(Object o, Response response) { // retrieve schedule from REST Schedules scheduleList = (Schedules) o; if (scheduleList == null) { Log.d(TAG, "No schedules!"); return; } sendSchedules(scheduleList.getLinks()); } @Override public void failure(RetrofitError retrofitError) { Log.d(TAG, retrofitError.getMessage()); } }; mMethods.getSchedules(mConferenceName, callback);}

Android Wear: Get Shedules

1

2

3

#Devoxx #smartvoxx @sarbogast @eloudsa

Pebble

Pebble app

Pebble App

JS module Internet

AppMessage

XHR

#Devoxx #smartvoxx @sarbogast @eloudsa

static bool load_site_list() { if(accessToken && sizeof(accessToken) > 0) { DictionaryIterator *iter; app_message_outbox_begin(&iter); if (iter == NULL) { APP_LOG(APP_LOG_LEVEL_DEBUG, "null iter"); return false; } Tuplet message_type_tuple = TupletInteger(MESSAGE_TYPE, LOAD_SITE_LIST); dict_write_tuplet(iter, &message_type_tuple); Tuplet access_token_tuple = TupletCString(ACCESS_TOKEN, accessToken); dict_write_tuplet(iter, &access_token_tuple); Tuplet refresh_token_tuple = TupletCString(REFRESH_TOKEN, refreshToken); dict_write_tuplet(iter, &refresh_token_tuple); dict_write_end(iter); app_message_outbox_send(); return true; } else { return false; }}

Pebble to phone

#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

#Devoxx #smartvoxx @sarbogast @eloudsa

Phone communication

#Devoxx #smartvoxx @sarbogast @eloudsa

Watch Connectivity

Apple Watch

update application context

send message

transfer user info

transfer file

replace

live

queue

big data

#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

#Devoxx #smartvoxx @sarbogast @eloudsa

Google API ClientDevice

Google Play Services

Your App

Google API Client

Google Play services library Message API

Data API

Node API

#Devoxx #smartvoxx @sarbogast @eloudsa

public class ScheduleActivity extends Activity {

// Google Play Servicesprivate GoogleApiClient mApiClient;

…@Overrideprotected void onStart() { super.onStart(); mApiClient = new GoogleApiClient.Builder(this) .addApi(Wearable.API) .addConnectionCallbacks(this) .build(); mApiClient.connect(); } @Overrideprotected void onStop() { if ((mApiClient != null) && (mApiClient.isConnected())) { Wearable.DataApi.removeListener(mApiClient, this); mApiClient.disconnect(); } super.onStop(); }

Google API Client

1

2

3

#Devoxx #smartvoxx @sarbogast @eloudsa

Connected nodes

Phone Watch

Node Node

Message APIWearableListenerService

onMessageReceived()

MessageApi

sendMessage()

#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

#Devoxx #smartvoxx @sarbogast @eloudsa

public class WearService extends WearableListenerService { …@Overridepublic 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

#Devoxx #smartvoxx @sarbogast @eloudsa

Data Synchronisation

Phone Watch

Node Node

Data API.putDataItem()

Wearable.DataApi

onDataChanged()

DataApi.DataListener

#Devoxx #smartvoxx @sarbogast @eloudsa

// send Schedules to the watchprivate 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

#Devoxx #smartvoxx @sarbogast @eloudsa

@Overridepublic 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; } }}

12

3

4

Data API: Data changed

#Devoxx #smartvoxx @sarbogast @eloudsa

Caching

#Devoxx #smartvoxx @sarbogast @eloudsa

private func saveSchedulesFromData(data: NSData, inContext context: NSManagedObjectContext) { context.performBlockAndWait { () -> Void in if data.length > 0 { do { let schedulesDict = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) if let schedulesDict = schedulesDict as? NSDictionary, schedulesArray = schedulesDict["links"] as? NSArray { guard let devoxx15 = self.getOrCreateDevoxx15(inContext: context) else { return } var schedules = [Schedule]() for scheduleDict in schedulesArray { let scheduleDict = scheduleDict as? NSDictionary let schedule = self.getOrCreateScheduleForHref(scheduleDict["href"] as! String, inContext: context)! schedule.title = scheduleDict["title"] as? String schedule.href = scheduleDict["href"] as? String schedule.conference = devoxx15 self.saveContext(context) schedules.append(schedule) } devoxx15.schedules = NSOrderedSet(array: schedules) self.saveContext(context) } } catch let jsonError as NSError { print(jsonError) } } } }

Core Data on Apple Watch

Demo

#Devoxx #smartvoxx @sarbogast @eloudsa

#Devoxx #smartvoxx @sarbogast @eloudsa

Wear

Data Item (Cache)

.getInt()

DataMap

.getDataIetms()

DataApi

wear://path_to_data

12

Data caching

MessageApi

sendMessage()

3

4

Demo

#Devoxx #smartvoxx @sarbogast @eloudsa

#Devoxx #smartvoxx @sarbogast @eloudsa

App structure and distribution

#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…

#Devoxx #smartvoxx @sarbogast @eloudsa

Android Wear

#Devoxx #smartvoxx @sarbogast @eloudsa

Prepare the build

• Include permissions required by Wear into Phone (Manifest)

• Use same package name and version number (build.gradle)

#Devoxx #smartvoxx @sarbogast @eloudsa

Generate signed APK

#Devoxx #smartvoxx @sarbogast @eloudsa

Mobile APK embeds WearableMobile App Module

Code

Resources

Wearable App

Wear App Module

Code

Resources

#Devoxx #smartvoxx @sarbogast @eloudsa

Publishing: Select Android Wear

#Devoxx #smartvoxx @sarbogast @eloudsa

Distribution

Companion App

Wearable App

Bluetooth

Companion App

Play Services

Android Wear

Wearable App

Smartvoxx

#Devoxx #smartvoxx @sarbogast @eloudsa

Pebble

Pebble App

JS module

#Devoxx #smartvoxx @sarbogast @eloudsa

Smartvoxx

• smartvoxx.com

Demo

#Devoxx #smartvoxx @sarbogast @eloudsa

#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

#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?)

#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

#Devoxx #smartvoxx @sarbogast @eloudsa

One more thing …

#Devoxx #smartvoxx @sarbogast @eloudsa

Smartvoxx on GithubAvailable in Black … … and White

http://github.com/smartvoxx