Devoxx 2015 presentation
We will show you the same application, developed for the three most renowned smartwatch platforms, namely the Pebble, Android Wear and Apple Watch. We will show you the tools, languages, APIs and capabilities of each platform so that you can get a feel for the kind of opportunities offered by this new usage paradigm and the investment required to leverage those opportunities. Be advised there will be some C, Java and Swift in this presentation.
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
• No kitten but a dog
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?
24. #Devoxx #smartvoxx @sarbogast @eloudsa
• Form factor: 38 mm and 42 mm (Square)
• Four kinds of applications: apps, notifications, glances,
complications
• Design guidelines: personal communication, holistic design,
lightweight interaction
Design constraints on Apple Watch
25. #Devoxx #smartvoxx @sarbogast @eloudsa
• Form factors
• Kinds of applications
• Design guidelines
Design constraints on Android Wear
48. #Devoxx #smartvoxx @sarbogast @eloudsa
• Tools:
• Android SDK Tools
• Android SDK Platform-Tools
• Android SDK Build-Tools
• Android from API 18 (4.3.1) to API 22 (5.1.1) and higher
• SDK Platform
• Google APIs
• Android Wear Intel x86 System Image
• Google APIs Intel x86 Atom System Image
Required libs
49. #Devoxx #smartvoxx @sarbogast @eloudsa
• Extras:
• Android Support Repository
• Android Support Library
• Google Play Services
• Google Repository
Required libs
50. #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
80. #Devoxx #smartvoxx @sarbogast @eloudsa
Step 3: Get Schedules
… monday - Monday, 9th November 2015
… tuesday - Tuesday, 10th November 2015
… wednesday - Wednesday, 11th November 2015
… thursday - Thursday, 12th November 2015
… friday - Friday, 13th November 2015
81. #Devoxx #smartvoxx @sarbogast @eloudsa
Step 3: Get Schedules
• Google Play Services and the Data Layer API:
• Node API, Message API
• The watch sends a request to the phone
• The phone accesses the network to fetch the schedules
• The phone logs the schedules
84. #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
85. #Devoxx #smartvoxx @sarbogast @eloudsa
Node API
• Learn more about local or connected Nodes
• Display name
• Id to identify the node in the Android Wear network
• Nearby the local node
86. #Devoxx #smartvoxx @sarbogast @eloudsa
Message API
• One-way communication
• Message (Data Item) sent to the connected device
• path -> identifies the message
• payload -> small message payload
87. #Devoxx #smartvoxx @sarbogast @eloudsa
Data Item
Path Payload
/path/to/your/data
Byte array
max: 100 Kb
Asset (Binary Blob) can go beyond this limitation of 100Kb.
Requires the Data API.
88. #Devoxx #smartvoxx @sarbogast @eloudsa
Watch
MessageApi
sendMessage()
Data Layer
Phone
WearableListenerService
onMessageReceived()
Data Layer
Bluetooth/Wi-Fi
MessageEvent
getData()
getPath()
Data Item
Message API
89. #Devoxx #smartvoxx @sarbogast @eloudsa
Wi-Fi
Fallback solution when Bluetooth not
available.
Unable to connect on remote servers.
90. #Devoxx #smartvoxx @sarbogast @eloudsa
Watch part
• Declare the Google API Client
• Connect-Disconnect to-from Google Play Services
• Define the message path
• Add a button listener
• Send the message to the phone to get the schedules
91. #Devoxx #smartvoxx @sarbogast @eloudsa
Phone part
• WearableListenerService: receive events from the Data Layer
• Process message path “/schedules” (onMessageReceived)
• Retrieves schedules with Retrofit (REST API Client)
• Logs schedules on the console
102. #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(SCHEDULES_PATH)) {
retrieveSchedules();
return;
}
}
Process “/schedules”
103. #Devoxx #smartvoxx @sarbogast @eloudsa
public class WearService extends WearableListenerService {
…
// Retrieve schedules from Devoxx
private 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;
}
List<Link> links = scheduleList.getLinks();
for (Link link : links) {
Log.d(TAG, Utils.getLastPartUrl(link.getHref()) + " - " + link.getTitle());
}
}
@Override
public void failure(RetrofitError retrofitError) {
Log.d(TAG, retrofitError.getMessage());
}
};
mMethods.getSchedules(mConferenceName, callback);
}
Retrieve Schedules
104. #Devoxx #smartvoxx @sarbogast @eloudsa
Run on Phone
• Build and deploy on Phone and Watch
• Forwarding ports (adb forward tcp:…):
• Link Phone to Wear Emulator
• On the Watch, tap on “GET SCHEDULES” button
• Check the log output of the phone
109. #Devoxx #smartvoxx @sarbogast @eloudsa
… monday - Monday, 9th November 2015
… tuesday - Tuesday, 10th November 2015
… wednesday - Wednesday, 11th November 2015
… thursday - Thursday, 12th November 2015
… friday - Friday, 13th November 2015
Step 3: Done!
110. #Devoxx #smartvoxx @sarbogast @eloudsa
Step 3: Get Schedules
• If phone is connected, go through phone
• Otherwise access the network directly
• Transparent for the developer
• Access to the same network SDK as on the iPhone
(NSURLSession)
112. #Devoxx #smartvoxx @sarbogast @eloudsa
Step 3: Get Schedules
• Bypass App Transport Security
• Create Devoxx singleton to handle API client stuff in watch
extension
• Initialize session configuration
• Initialize session
• Call the API
• Parse JSON into dictionaries
• Log dictionaries to the console
119. #Devoxx #smartvoxx @sarbogast @eloudsa
Step 4: Show Schedules
• The phone sends the schedules to the watch (Data API)
• The watch receives the schedules
• The watch displays the schedules on a list view
120. #Devoxx #smartvoxx @sarbogast @eloudsa
Data API
• Support one-way or two-way data communication
• Sending Binary Blog (Asset)
• Synchronise data between connected devices
• Synchronise data when connection is re-established
• Data caching
121. #Devoxx #smartvoxx @sarbogast @eloudsa
Phone
Data Layer
Watch
Data Layer
.create(“/path”)
PutDataMapRequest
.putInt(KEY, data)
DataMap
.asPutDataRequest()
PutDataRequest
.putDataItem()
Wearable.DataApi
Data Item
(Shared)
.getInt()
DataMap
.getDataItem()
DataEvent
.fromDataItem(DataItem)
DataMapItem
onDataChanged()
DataApi.DataListenerBluetooth/Wi-Fi
Data API
122. #Devoxx #smartvoxx @sarbogast @eloudsa
@Override
protected void onResume() {
super.onResume();
// Retrieve the list of schedules
sendMessage(SCHEDULES_PATH, "get list of schedules");
}
Display schedules
123. #Devoxx #smartvoxx @sarbogast @eloudsa
public class WearService extends WearableListenerService {
// send Schedules to the watch
private void sendSchedules(List<Link> schedules) {
final PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(SCHEDULES_PATH);
ArrayList<DataMap> schedulesDataMap = new ArrayList<>();
// process each schedule
for (Link schedule : schedules) {
final DataMap scheduleDataMap = new DataMap();
// process and push schedule's data
// We need to add a timestamp to force a onDataChanged event on the remote device.
scheduleDataMap.putString("timestamp", new Date().toString());
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("/list", schedulesDataMap);
// send the list
if (mApiClient.isConnected()) {
Wearable.DataApi.putDataItem(mApiClient, putDataMapRequest.asPutDataRequest());
}
}
Send Schedules
1
2
3
5
6
4
130. #Devoxx #smartvoxx @sarbogast @eloudsa
// Inner class providing the WearableListview's adapter
public class ListViewAdapter extends WearableListView.Adapter {
…
// Create new views for list items
// (invoked by the WearableListView's layout manager)
@Override
public WearableListView.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// Inflate our custom layout for list items
return new ItemViewHolder(new SettingsItemView(mContext));
}
…
Animation
131. #Devoxx #smartvoxx @sarbogast @eloudsa
public final class SettingsItemView extends FrameLayout implements
WearableListView.OnCenterProximityListener {
private TextView description;
public SettingsItemView(Context context) {
super(context);
View.inflate(context, R.layout.schedule_row_activity, this);
description = (TextView) findViewById(R.id.description);
}
@Override
public void onCenterPosition(boolean b) {
description.animate().scaleX(1f).scaleY(1f).alpha(1);
}
@Override
public void onNonCenterPosition(boolean b) {
description.animate().scaleX(0.8f).scaleY(0.8f).alpha(0.6f);
}
}
Animation
134. #Devoxx #smartvoxx @sarbogast @eloudsa
Run the app
• Build and deploy on Phone and Watch
• Forwarding ports (adb forward tcp:…):
• Link Phone to Wear Emulator
• Data are retrieved and displayed from Phone
137. #Devoxx #smartvoxx @sarbogast @eloudsa
Step 4: Show Schedules
• Model classes to store data (Model)
• Storyboard to layout screens (View)
• InterfaceControllers to configure and react (Controllers)
139. #Devoxx #smartvoxx @sarbogast @eloudsa
Step 4: Show Schedules
• Create Schedule class: initializer and overridden description
• Replace dictionary by class in callback
• Remove label and add table to layout
• Specify identifier for row controller
• Create ScheduleRowController class
• Link ScheduleRowController in storyboard
• Label outlet in ScheduleRowController
• Table outlet in interface controller
140. #Devoxx #smartvoxx @sarbogast @eloudsa
Step 4: Show Schedules
• Call Devoxx loading method in willActivate and initialize table
• Remove Devoxx call from ExtensionDelegate
• RUN!
141. #Devoxx #smartvoxx @sarbogast @eloudsa
class Schedule: NSObject {
var title:String?
var href:NSURL?
init(fromDictionary dictionary:NSDictionary){
guard let title = dictionary["title"] as? String else {
print("Cannot find title")
return
}
guard let href = dictionary["href"] as? String else {
print("Cannot find href")
return
}
self.title = title
self.href = NSURL(string: href)
}
override var description:String {
return self.title!
}
}
Schedule model class
142. #Devoxx #smartvoxx @sarbogast @eloudsa
func loadSchedulesForConference(conference:String, callback: ([Schedule]) -> (Void)) {
let task = session.dataTaskWithURL(schedulesURL) { (data: NSData?, response:NSURLResponse?,
error:NSError?) -> Void in
do {
let schedulesDict=try NSJSONSerialization.JSONObjectWithData(data, options:.AllowFragments)
guard let schedulesArray = schedulesDict["links"] as? [NSDictionary] else {
print("No links array in parsed schedules")
return
}
var schedules = [Schedule]()
for scheduleDict in schedulesArray {
schedules.append(Schedule(fromDictionary: scheduleDict))
}
callback(schedules)
} catch let jsonError { print(jsonError) }
}
task.resume()
}
Replace dictionary by model
151. #Devoxx #smartvoxx @sarbogast @eloudsa
Step 5: Select a Schedule
• Store Schedule’s data on Tag
• Add ClickListener on WearableListView
• Attach the listener
• Retrieve Schedule’s data from Tag
• Display the selected item
152. #Devoxx #smartvoxx @sarbogast @eloudsa
@Override
public void onBindViewHolder(WearableListView.ViewHolder holder,
int position) {
// retrieve the text view
ItemViewHolder itemHolder = (ItemViewHolder) holder;
TextView view = itemHolder.textView;
// retrieve, transform and display the schedule's day
Schedule schedule = mDataset.get(position);
String scheduleDay = schedule.getTitle().replace(",", "n");
view.setText(scheduleDay);
// replace list item's metadata
holder.itemView.setTag(schedule);
}
Store Schedule’s data
1
2
153. #Devoxx #smartvoxx @sarbogast @eloudsa
public class ScheduleActivity extends Activity implements
WearableListView.ClickListener,
GoogleApiClient.ConnectionCallbacks,
DataApi.DataListener {
@Override
public void onClick(WearableListView.ViewHolder
viewHolder) {
}
}
Add ClickListener
154. #Devoxx #smartvoxx @sarbogast @eloudsa
@Override
protected void onCreate(Bundle savedInstanceState) {
…
// Assign the adapter
listViewAdapter = new ListViewAdapter(ScheduleActivity.this, new
ArrayList<Schedule>());
listView.setAdapter(listViewAdapter);
// Set the click listener
listView.setClickListener(ScheduleActivity.this);
}
Attach the ClickListener
155. #Devoxx #smartvoxx @sarbogast @eloudsa
@Override
public void onClick(WearableListView.ViewHolder viewHolder) {
Schedule schedule = (Schedule) viewHolder.itemView.getTag();
if (schedule == null) {
return;
}
Toast.makeText(ScheduleActivity.this, "You tap on: " +
schedule.getDay(), Toast.LENGTH_SHORT).show();
}
Retrieve and display schedule
1
2
156. #Devoxx #smartvoxx @sarbogast @eloudsa
Run the app
• Build and deploy on the Watch
• Forwarding ports (adb forward tcp:…):
• Link Phone to Wear Emulator
• Tap on a schedule
159. #Devoxx #smartvoxx @sarbogast @eloudsa
Step 5: Select a Schedule
• Add title to interface controller
• New interface controller in storyboard
• Push segue from first to second interface controller
• Create ScheduleInterfaceController class
• Link it to storyboard
• Add schedule member variable
• Override contextForSegueWithIdentifier
• Catch context in ScheduleInterfaceController
• Set title in willActivate
167. #Devoxx #smartvoxx @sarbogast @eloudsa
Phone
Data Layer
Watch
Data Layer
.create(“/path”)
PutDataMapRequest
.putInt(KEY, data)
DataMap
.asPutDataRequest()
PutDataRequest
.putDataItem()
Wearable.DataApi
Data Item
(Shared)
.getInt()
DataMap
.getDataItem()
DataEvent
.fromDataItem(DataItem)
DataMapItem
onDataChanged()
DataApi.DataListener
Data API
168. #Devoxx #smartvoxx @sarbogast @eloudsa
Wear
Data Item
(Cache)
.getInt()
DataMap
.getDataIetms()
DataApi
wear://path_to_data
1
2
Fetch from local cache
MessageApi
sendMessage()
3
4
169. #Devoxx #smartvoxx @sarbogast @eloudsa
public class WearService extends WearableListenerService {
// send Schedules to the watch
private void sendSchedules(List<Link> schedules) {
final PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(SCHEDULES_PATH);
ArrayList<DataMap> schedulesDataMap = new ArrayList<>();
// process each schedule
for (Link schedule : schedules) {
final 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("/list", schedulesDataMap);
// send the list
if (mApiClient.isConnected()) {
Wearable.DataApi.putDataItem(mApiClient, putDataMapRequest.asPutDataRequest());
}
}
Changes on Phone: No timestamp
1
170. #Devoxx #smartvoxx @sarbogast @eloudsa
@Override
protected void onResume() {
super.onResume();
// Retrieve and display the list of schedules
getSchedules(SCHEDULES_PATH);
}
Display data items
171. #Devoxx #smartvoxx @sarbogast @eloudsa
private void getSchedules(final String pathToContent) {
Uri uri = new Uri.Builder()
.scheme(PutDataRequest.WEAR_URI_SCHEME)
.path(pathToContent)
.build();
Wearable.DataApi.getDataItems(mApiClient, uri)
.setResultCallback(
new ResultCallback<DataItemBuffer>() {
@Override
public void onResult(DataItemBuffer dataItems) {
if (dataItems.getCount() == 0) {
// refresh the list of schedules from Mobile
sendMessage(SCHEDULES_PATH, "get list of schedules");
return;
}
…
// retrieve and display the schedule from the cache
SchedulesListWrapper schedulesListWrapper = new SchedulesListWrapper();
final List<Schedule> schedulesList = schedulesListWrapper.getSchedulesList(dataMap);
runOnUiThread(new Runnable() {
@Override
public void run() {
// hide the progress bar
findViewById(R.id.progressBar).setVisibility(View.GONE);
listViewAdapter.refresh(schedulesList);
}
});
}
}
);
}
Retrieve data: mobile or cache?
1
2
3
4
174. #Devoxx #smartvoxx @sarbogast @eloudsa
Step 6: Get and Cache Schedules
• Create activity indicator animation
• Link with CoreData framework
• Create object model
• Create DevoxxCache class
• Setup Core Data stack in DevoxxCache
• Model Schedule and Conference in object model
• Delete Schedule Model class
• Generate NSManagedObject subclasses
175. #Devoxx #smartvoxx @sarbogast @eloudsa
Step 6: Get and Cache Schedules
• Add empty getSchedules() method to DevoxxCache
• Add empty saveSchedules() method to DevoxxCache
• Modify loadSchedules in Devoxx class
176. #Devoxx #smartvoxx @sarbogast @eloudsa
Create activity indicator animation
https://github.com/mikeswanson/JBWatchActivityIndicator
• Add image to interface controller
• Scale mode: center
• Height and width relative to
container
• Hidden
• activityIndicator outlet in interface
controller
194. #Devoxx #smartvoxx @sarbogast @eloudsa
Step 7: Get Talk
• Create the Activity
• Layouts: GridView, Card, Custom
• GridViewPagerAdapter
• Create the Fragments
• EventBus
• Retrieve Talks and Speakers from Data Api
• Requests sent through the Message Api
• Bonus: Follow on Twitter (Confirmation Activity)
196. #Devoxx #smartvoxx @sarbogast @eloudsa
Fragment Fragment
Fragment Fragment
• TalkFragment
• TalkSummaryFragment
• TalkSpeakerFragment
FragmentGridPagerAdapter
TalkActivity
197. #Devoxx #smartvoxx @sarbogast @eloudsa
• Define the layout
• Create the fragments
• Create the Adapter
• Link the Adapter
Create the GridViewPager
206. #Devoxx #smartvoxx @sarbogast @eloudsa
public class TalkSummaryFragment extends Fragment {
…
description.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mTalkSummary == null) {
return;
}
if (mEllipsize) {
description.setText(mTalkSummary);
} else {
description.setText(StringUtils.abbreviate(mTalkSummary, ELLIPSIS_SIZE));
}
mEllipsize = !mEllipsize;
// Force the CardScrollView to reset to its initial position
mainView.findViewById(R.id.card_scroll_view).setScrollX(0);
mainView.findViewById(R.id.card_scroll_view).setScrollY(0);
}
});
Summary: Expand content
212. #Devoxx #smartvoxx @sarbogast @eloudsa
public void onEvent(AddFavoriteEvent addFavoritesEvent) {
…
}
EventBus - Receive events
Required to register/unregister to the bus
224. #Devoxx #smartvoxx @sarbogast @eloudsa
Step 8: Set Favorites
• Retrieve, add, remove favorites on the calendar
• Add CalendarHelper on the phone
• Favorite messages sent over DataApi (retrieved, added, removed)
• Add reminders using AlarmManager
• Manage a manual delete from the Calendar with a synchronisation of
the SlotsActivity or TalkActivity
• Send a message when required (retrieve) or when the favorite has
changed (add or remove)
• Use the EventBus to synchronise the components (Activity, Fragments)
• ConfirmationActivity on favorite’s action
238. #Devoxx #smartvoxx @sarbogast @eloudsa
Step 8: Managing Favorites
• Add a force touch menu to SlotController
• Handle menu actions
• Add scheduleNotifications() method to talk to the phone
• Import WatchConnectivity framework
• Start WatchConnectivity session
• Receive messages in AppDelegate on iPhone
239. #Devoxx #smartvoxx @sarbogast @eloudsa
class SlotController: WKInterfaceController {
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
self.updateMenu()
}
func updateMenu() {
self.clearAllMenuItems()
if let talkSlot = self.slot as? TalkSlot {
if let favorite = talkSlot.favorite?.boolValue where favorite {
self.addMenuItemWithImageNamed("FavoriteOffMenu", title: NSLocalizedString("Remove
from Favorites", comment: ""), action: "favoriteMenuSelected")
self.favoriteImage.setImageNamed("FavoriteOn")
} else {
self.addMenuItemWithImageNamed("FavoriteOnMenu", title: NSLocalizedString("Add to
Favorites", comment: ""), action: "favoriteMenuSelected")
self.favoriteImage.setImageNamed("FavoriteOff")
}
self.addMenuItemWithItemIcon(WKMenuItemIcon.Decline, title: NSLocalizedString("Cancel",
comment: ""), action: "cancelMenuSelected")
}
}
}
Add force touch menu
240. #Devoxx #smartvoxx @sarbogast @eloudsa
class SlotController: WKInterfaceController {
[…]
@IBAction func favoriteMenuSelected() {
if let talkSlot = self.slot as? TalkSlot {
DataController.sharedInstance.swapFavoriteStatusForTalkSlot(talkSlot, callback:
{ (talkSlot:TalkSlot) -> Void in
self.slot = talkSlot
self.updateMenu()
})
}
}
@IBAction func cancelMenuSelected() {}
}
Handle menu actions
241. #Devoxx #smartvoxx @sarbogast @eloudsa
class SlotController: WKInterfaceController {
[…]
@IBAction func favoriteMenuSelected() {
if let talkSlot = self.slot as? TalkSlot {
DataController.sharedInstance.swapFavoriteStatusForTalkSlot(talkSlot, callback:
{ (talkSlot:TalkSlot) -> Void in
self.slot = talkSlot
self.updateMenu()
self.scheduleNotification()
})
}
}
@IBAction func cancelMenuSelected() {}
}
Handle menu actions
255. #Devoxx #smartvoxx @sarbogast @eloudsa
<application>
…
<service android:name=".service.EventService"/>
</application
Alarm: EventService
Update the manifest of the phone
256. #Devoxx #smartvoxx @sarbogast @eloudsa
public class EventService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
…
// remove the event from the calendar
CalendarHelper calendarHelper = new CalendarHelper(this);
calendarHelper.removeEvent(eventId);
…
sendFavorite(talkId, 0L);
..
}
Phone: EventService
259. #Devoxx #smartvoxx @sarbogast @eloudsa
Step 9: Notifications
• Schedule notifications on the iPhone
• Custom notification controller
260. #Devoxx #smartvoxx @sarbogast @eloudsa
private func updateLocalNotificationWithMessage(message:[String:AnyObject]){
if let talkSlot = message["talkSlot"] as? NSDictionary {
let id = talkSlot["talkId"] as? String
for notification in UIApplication.sharedApplication().scheduledLocalNotifications! {
if let userInfo = notification.userInfo {
if let talkId = userInfo["id"] as? String {
if talkId == id {
UIApplication.sharedApplication().cancelLocalNotification(notification)
}
}
}
}
let favorite = talkSlot["favorite"] as? NSNumber
if let fav = favorite?.boolValue where fav {
let title = talkSlot["title"] as? String
let room = talkSlot["room"] as? String
let fromTimeMillis = talkSlot["fromTimeMillis"] as? NSNumber
let fromTime = talkSlot["fromTime"] as? String
let toTime = talkSlot["toTime"] as? String
let date = NSDate(timeIntervalSince1970: fromTimeMillis!.doubleValue / 1000)
let notification = UILocalNotification()
notification.fireDate = date.dateByAddingTimeInterval(-10*60)
notification.timeZone = NSTimeZone.localTimeZone()
notification.userInfo = talkSlot as [NSObject : AnyObject]
notification.alertTitle = title
notification.alertBody = String(format: NSLocalizedString("From %@ to %@ in %@", comment: ""), arguments: [fromTime!, toTime!, room!])
UIApplication.sharedApplication().scheduleLocalNotification(notification)
}
}
}
Actually schedule notifications
277. #Devoxx #smartvoxx @sarbogast @eloudsa
Step 11: Release the App
• Package the Apple Watch app with the iPhone app
• Release the iPhone app like any other
• Wait for review…
• Wait again…
• Wait some more…
278. #Devoxx #smartvoxx @sarbogast @eloudsa
A word about Pebble
• Language: either C or Javascript
• Development environment: either text editor or CloudPebble
• Platform support: both iOS and Android (+SDKs)
• Devices: Pebble Classic, Pebble Time, Pebble Time Round
• Distribution: via the Pebble app
280. #Devoxx #smartvoxx @sarbogast @eloudsa
function loadShutterGroupList(accessToken, refreshToken, args) {
console.log("Loading shutter group list for access token " + accessToken + " and site " + args[0]);
var response;
var req = new XMLHttpRequest();
// build the GET request
var url = "https://api.myfox.me:443/v2/site/" + args[0] + "/group/shutter/items?access_token=" + accessToken;
console.log("GETting " + url);
req.open('GET', url, 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 shutterGroupList;
if (response.status === 'OK') {
shutterGroupList = response.payload.items;
var msg = {};
msg.messageType = MessageType.SHUTTER_GROUP_LIST;
for(var i = 0; i < shutterGroupList.length; i++){
var shutterGroup = shutterGroupList[i];
msg['' + shutterGroup.groupId] = shutterGroup.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 shutter groups."});
}
} else if(req.status == 401 && refreshToken){
getNewAccessToken(refreshToken, loadShutterGroupList, args);
} else {
console.log("Request returned error code " + req.status.toString());
Pebble.sendAppMessage({messageType:MessageType.ERROR, errorMessage:"Could not load shutter groups."});
}
}
};
req.send(null);
}
Pebble code
281. #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
282. #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?)
283. #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