MAKING APPS FOR ANDROID WEAR LUIS DE LA ROSA LEAD APP DEVELOPER CAPITAL ONE
MAKING APPS FOR ANDROID WEARLUIS DE LA ROSALEAD APP DEVELOPER
CAPITAL ONE
WEAR OVERVIEWCurrently targeting only watches
Several manufacturers and modelsBuilds upon your Android experience
WHAT IS NOT IN WEARwebkitprint
appwidgethardware.usb
WHAT IS ONLY IN WEARWearable UI Library
Wearable Data Layer
SETUP DEV FOR WEARAndroid Studio
SDK 20 4.4W KITKAT_WATCHPair with Emulator
adb -d forward tcp:5601 tcp:5601bluetooth
ANDROID WEAR PROJECTwear module
handheld modulesubclass Activity, not WatchActivity (fixed in Studio 0.8.1)
DESIGN + UXsmall screen: roughly 320x320
timeout -> homeStream of CardsSwipes and Taps
GLANCEABLE UI
PATTERNSCard
Action Button1D Picker2D Picker
Selection ListConfirmation
Exit
NOTIFICATIONS AND APPS
HANDHELD AND WEARABLE APPS
HELLO WEAR WORLD APP
SQUARE VS ROUNDWatchViewStub
rectLayout / roundLayoutBoxInsetLayout
app:layout_box="bottom" (or all, top|left, etc)
EMULATORRound
Workaround: Use Host GPU off to truly roundSquare
Can make custom 320x320HAXM + Intel image
VOICEvoice trigger
custom voice trigger with label attribute and launcher intent-filterspeech input
WEAR LIBRARIESInside of Google Play Services
com.google.android.gms.wearable17 Interfaces12 Classes
WEAR UI LIBRARYaka Wearable Support Library
android.support.wearable2 Classes in .activity
7 Interfaces and 19 Classes in .view
CARDSCardFragment
CardFrameCardScrollView
GRIDVIEWPAGERFragmentGridPagerAdapter
getBackground(row, column) - parallaxgetRowCount() / getColumnCount(row)
getFragment(row, column)getCurrentColumnForRow(row, currentColumn)
LISTS
LISTSWearableListView
.Item.ViewHolder
.Adapter.ClickListener
EXITINGswipeToDismiss
DeviceDefault theme<style name="AppTheme" parent="@android:style/Theme.DeviceDefault.Light"> <item name="android:windowSwipeToDismiss">false</item></style>
DismissOverlayView + GestureDetector for long press
ALLOWING USER TO CANCELDelayedConfirmationView
SHOWING CONFIRMATION ANIMATIONConfirmationActivity
UI EXAMPLES
WEARABLE DATA LAYER APIPurpose
GoogleApiClientData Items
AssetMessage
NodeWearableListenerService
DataListener / MessageListener / NodeListener
GOOGLEAPICLIENTbuild with Wearable.API
attach callbacks and listenersconnect
@Overridepublic void onCreate(Bundle b) { super.onCreate(b); mGoogleApiClient = new GoogleApiClient.Builder(this) .addApi(Wearable.API) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .build();}
CONNECTING@Overrideprotected void onStart() { super.onStart(); mGoogleApiClient.connect();}
@Override //ConnectionCallbackspublic void onConnected(Bundle connectionHint) { Wearable.DataApi.addListener(mGoogleApiClient, this); Wearable.MessageApi.addListener(mGoogleApiClient, this); Wearable.NodeApi.addListener(mGoogleApiClient, this);}
DISCONNECTING@Overrideprotected void onStop() { Wearable.DataApi.removeListener(mGoogleApiClient, this); Wearable.MessageApi.removeListener(mGoogleApiClient, this); Wearable.NodeApi.removeListener(mGoogleApiClient, this); mGoogleApiClient.disconnect(); super.onStop();}
DECIDING WHICH TO USEReplicate data?
Less than 100KB, use Data*100KB or more, use Asset
Message-based
DATAITEM (SENDING SMALL ITEMS)Payload
Path
DATAMAP (SENDING SMALL ITEMS #2)Like a Bundle
public void syncCount() { PutDataMapRequest dataMap = PutDataMapRequest.create("/count"); dataMap.getDataMap().putInt(COUNT_KEY, count++); PutDataRequest request = dataMap.asPutDataRequest(); PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi .putDataItem(mGoogleApiClient, request);}
ASSET (SENDING LARGE BINARY DATA)createFrom*
PutDataRequest.putAsset()PutDataMapRequest.getDataMap().putAsset()
Asset.createFromBytes(byteStream.toByteArray());
private void sendPhoto(Asset asset) { PutDataMapRequest dataMap = PutDataMapRequest.create(IMAGE_PATH); dataMap.getDataMap().putAsset(IMAGE_KEY, asset); dataMap.getDataMap().putLong("time", new Date().getTime()); PutDataRequest request = dataMap.asPutDataRequest(); Wearable.DataApi.putDataItem(mGoogleApiClient, request) .setResultCallback(new ResultCallback<DataItemResult>() { @Override public void onResult(DataItemResult dataItemResult) { LOGD(TAG, "Sending image was successful: " + dataItemResult.getStatus() } });
}
MESSAGEWearable.MessageApi.sendMessage()
private void sendStartActivityMessage(String node) { Wearable.MessageApi.sendMessage( mGoogleApiClient, node, START_ACTIVITY_PATH, new byte[0]).setResultCallback( new ResultCallback<SendMessageResult>() { @Override public void onResult(SendMessageResult sendMessageResult) { if (!sendMessageResult.getStatus().isSuccess()) { Log.e(TAG, "Failed to send message with status code: " + sendMessageResult.getStatus().getStatusCode()); } } } );}
PENDINGRESULTasync
setResultCallback();
syncawait();
WEARABLELISTENERSERVICEcreate in both wearable and handheld apps
<service android:name=".YourListenerService"> <intent-filter> <action android:name="com.google.android.gms.wearable.BIND_LISTENER" /> </intent-filter></service>
Binder.clearCallingIdentity() / restoreCallingIdentity()
LISTENING FOR DATAMAPSonDataChanged()
@Overridepublic void onDataChanged(DataEventBuffer dataEvents) { LOGD(TAG, "onDataChanged: " + dataEvents); final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents); dataEvents.release(); // Loop through the events for (DataEvent event : events) { Uri uri = event.getDataItem().getUri(); String path = uri.getPath(); // Find the path we're interested in and work with that event if (COUNT_PATH.equals(path)) { } }}
LISTENING FOR MESSAGESonMessageReceived()
public void onMessageReceived(final MessageEvent messageEvent) { LOGD(TAG, "onMessageReceived() A message from watch was received:" + messageEvent .getRequestId() + " " + messageEvent.getPath());}
LISTENING FOR PEERSonPeerConnected() / onPeerDisconnected()
@Overridepublic void onPeerConnected(Node peer) { LOGD(TAG, "onPeerConnected: " + peer);}
@Overridepublic void onPeerDisconnected(Node peer) { LOGD(TAG, "onPeerDisconnected: " + peer);}
LISTENING IN AN ACTIVITYDataApi.DataListener
MessageApi.MessageListenerNodeApi.NodeListener
GoogleApiClient build / connect / addListener / removeListener/ disconnect
onDataChanged() / onMessageReceived() /onPeerConnected() / onPeerDisconnected()
FREEZABLEInterface for data objects that support being frozen into
immutable representations..freeze();
FreezableUtils.freezeIterable();
DATA LAYER EXAMPLES
PACKAGING WEAR APP
PROGUARDWearableListView
NEXT STEPSStack Overflow - use android-wear tag
SDK ExamplesSet up Emulators (and maybe buy a Watch)
Send feedbackStop by our booth
THANKS!Luis de la Rosa
twitter.com/@louielouiegoogle.com/+luisdelarosa
luisdelarosa.com/wear