Date post: | 21-Jan-2018 |
Category: |
Technology |
Upload: | wajug |
View: | 668 times |
Download: | 0 times |
#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
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
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
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
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
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
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
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
#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
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
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
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
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
#Devoxx #smartvoxx @sarbogast @eloudsa
Wear
Data Item (Cache)
.getInt()
DataMap
.getDataIetms()
DataApi
wear://path_to_data
12
Data caching
MessageApi
sendMessage()
3
4
#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
Prepare the build
• Include permissions required by Wear into Phone (Manifest)
• Use same package name and version number (build.gradle)
#Devoxx #smartvoxx @sarbogast @eloudsa
Mobile APK embeds WearableMobile App Module
Code
Resources
Wearable App
Wear App Module
Code
Resources
#Devoxx #smartvoxx @sarbogast @eloudsa
Distribution
Companion App
Wearable App
Bluetooth
Companion App
Play Services
Android Wear
Wearable App
Smartvoxx
#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
Smartvoxx on GithubAvailable in Black … … and White
http://github.com/smartvoxx