2. DroidCon Montréal
2015
2
Building your Super Wearable App
• You've got an idea for the next super app?
• But don't know where to start with Android Wear
development?
• Let's spend some time stretching those Android Wear
development muscles.
3. DroidCon Montréal
2015
3
What is Android Wear?
• An Android platform for your wrist that can run native apps.
• A stream of cards that you can swipe through and interact with.
• These are notifications delivered from your phone, or displayed directly by
apps on the watch.
• Sensors like step counters, barometer, heart-rate sensor, and accelerometer.
• Customizable watch faces, and the ability to design your own faces.
• A communication channel between your watch and your phone.
• A set of custom components to make application development for the watch
easier.
4. DroidCon Montréal
2015
4
Types of Android Wear Applications
• Extended Notifications in Phone Apps
• Notifications appear in the watch stream and can be customized with
background images, pages, and layout modifications.
• Phone App with Embedded Wear App
• More powerful and flexible: a full, native,Android app is delivered to the Wear
device which can take full advantage of the platform.
5. DroidCon Montréal
2015
5
First Wear Wonder: Enhanced Notifications
• Notifications can be extended for wear using the
Android Support Library and the
NotificationCompat.Builder class.
• The NotificationCompat.WearableExtender class provides
properties that Android Wear uses to change the
appearance and behaviour of the notifications on the
watch.
6. DroidCon Montréal
2015
6
Extending Notifications: Step One
dependencies {
compile 'com.android.support:support-v4:22.0.0'
}
Add the Android Support library to your dependencies in your
App's build.gradle file.
7. DroidCon Montréal
2015
7
Extending Notifications: Step Two
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("My notification")
.setContentText("When in danger,nWhen in doubt,nRun in circles,nScream and shout!");
int mNotificationId = 1;
NotificationManager mNotifyMgr =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
mNotifyMgr.notify(mNotificationId, mBuilder.build());
Modify your notification building code to extend it for Android
Wear.
8. DroidCon Montréal
2015
int mNotificationId = 1;
NotificationManager mNotifyMgr =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
mNotifyMgr.notify(mNotificationId, mBuilder.build());
8
Extending Notifications: Step Two
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("My notification")
.setContentText("When in danger,nWhen in doubt,nRun in circles,nScream and shout!");
9. DroidCon Montréal
2015
int mNotificationId = 1;
NotificationManager mNotifyMgr =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
mNotifyMgr.notify(mNotificationId, mBuilder.build());
8
Extending Notifications: Step Two
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("My notification")
.setContentText("When in danger,nWhen in doubt,nRun in circles,nScream and shout!");
NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender()
.setGravity(Gravity.TOP)
.setHintScreenTimeout(NotificationCompat.WearableExtender.SCREEN_TIMEOUT_LONG);
mBuilder.extend(wearableExtender);
10. DroidCon Montréal
2015
9
Extending Notifications:Wearable Only Actions
• Adding wearable-only actions can be done by adding one or more actions
to the wearable extender.
// Create an intent for the action
Intent actionIntent = new Intent(this, LoveItActivity.class);
PendingIntent actionPendingIntent =
PendingIntent.getActivity(this, 0, actionIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
// Create the action
NotificationCompat.Action action =
new NotificationCompat.Action.Builder(R.drawable.ic_love,
getString(R.string.ic_love_label), actionPendingIntent)
.build();
NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender()
.setGravity(Gravity.TOP)
.setHintScreenTimeout(NotificationCompat.WearableExtender.SCREEN_TIMEOUT_LONG);
.addAction(action)
11. DroidCon Montréal
2015
10
Extending Notifications: Adding Pages
• The WearableExtender provides the ability to add pages to a
notification that can be swiped through.
wearableExtender.addPage(
new NotificationCompat.Builder(this)
.extend(new NotificationCompat.WearableExtender()
.setBackground(BitmapFactory.decodeResource(getResources(), R.drawable.punk))
.setHintAvoidBackgroundClipping(true)
.setHintShowBackgroundOnly(true))
.build());
Source code for this sample app can be found at: http://goo.gl/Kxucib
12. DroidCon Montréal
2015
11
Extending Notifications: Fake Demo
• In lieu of super demo hardware, you instead receive a fake demo.
• The notification is sent, and on the Watch, the following pages are displayed.
13. DroidCon Montréal
2015
12
Native App Development
• A native Android Wear app will always be bundled
with an app that's installed from Google Play on the
Phone.
• Android Studio has templates to get you started.
• The Google Play Services Wearable library bridges the
gap between the phone and the watch.
14. DroidCon Montréal
2015
13
Wear Development Ideas
• Wear Apps can fire their own Notifications.Those notifications will not
be shown on the phone.
• It can be very helpful to share a bit of code between a Wear app and it's
Mobile App counterpart through a shared Android Library.
• The Google Play Services Wearable library provides data synchronization and
message passing, but only passes byte arrays. Libraries can make this much
easier, such as the Courier library by Denley Bihari, which will be discussed later.
• A watch isn't just for watch faces although you can make those as well.
15. DroidCon Montréal
2015
14
Getting Started With Wear Development
• Android Studio provides a Wear module template.This can be added into your
app, and with a little Gradle Glue, you've added a wear extension to your app.
16. DroidCon Montréal
2015
15
Finishing the Template Wizard
• Choosing the Display Notification activity leads to some customization.
• Hitting finish, creates a project with
two modules: mobile, and wear.
• The wear module contains two activities
and a broadcast receiver.
MyStub
BroadcastActivity
MyPostNotification
Receiver
MyDisplay
Activity
Creates NotificationBroadcasts Intent
17. DroidCon Montréal
2015
16
Running the Wear App During Development
• You're going to want to run the app, and debug it, during development.
• If you have a device with a charging dock that doubles as a USB bridge,
you can develop rapidly with the device on the dock.
• Otherwise you'll need to use Debugging over Bluetooth or make your own
cable.
18. DroidCon Montréal
2015
17
Enabling Debugging over Bluetooth
• First, on your Wear device you'll have to
enable developer settings by tapping on
the Settings > About > Build Number a few
times.
• Then in Settings > Developer Settings enable
ADB debugging and Debug over Bluetooth.
19. DroidCon Montréal
2015
18
Enabling Debugging over Bluetooth
• Next, in the Android Wear app on your
phone, open settings and turn on the
Debugging over Bluetooth
• Initially you will likely see host is
disconnected, so you will need to run
some ADB commands:
• adb forward tcp:4444 localabstract:/adb-hub
• adb connect localhost:4444
20. DroidCon Montréal
2015
19
The RestYou Already Know
• Your Android Wear device will show up
in Android Studio as another Android
device you can deploy to, and debug on.
• Transfer to the device over Bluetooth
takes quite a while, but it is conveniently
devoid of wires.
• Choose the wear module from the
configurations chooser, hit the run
button, and the app will run on your
watch.
21. DroidCon Montréal
2015
19
The RestYou Already Know
• Your Android Wear device will show up
in Android Studio as another Android
device you can deploy to, and debug on.
• Transfer to the device over Bluetooth
takes quite a while, but it is conveniently
devoid of wires.
• Choose the wear module from the
configurations chooser, hit the run
button, and the app will run on your
watch.
22. DroidCon Montréal
2015
20
Gradle Glue and Matryoshka Apps
• The Wear app's dependencies in
build.gradle look like this:
dependencies {
compile 'com.google.android.support:wearable:1.1.0'
compile 'com.google.android.gms:play-services-wearable:6.5.87'
}
• The Mobile app's dependencies
look like this:
dependencies {
wearApp project(':wear')
compile 'com.google.android.gms:play-services:+'
}
• Notice the special wearApp project statement.This is how the wear app
is embedded into the mobile app.Apps inside apps. Like Russian Dolls.
• Only a signed, release build, will automatically deploy the wear app to a
watch. Debug builds do not auto deploy their wear apps.
23. DroidCon Montréal
2015
21
Mobile and Wear app Symbiosis
• A Wear app is not very interesting without a Mobile app partnered with it.
• The Mobile app and Wear app share data, and can send messages to each
other.
• Let's quickly go through the Android Wear API for sharing data and sending
messages.
24. DroidCon Montréal
2015
22
Sharing Data and Sending Messages
• It all starts with the GoogleApiClient and the Wearable.API.
// In onCreate
GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
@Override
public void onConnected(Bundle connectionHint) {
Log.d(TAG, "onConnected: " + connectionHint);
// Now you can use the Data Layer API
}
@Override
public void onConnectionSuspended(int cause) {
Log.d(TAG, "onConnectionSuspended: " + cause);
}
})
.addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
@Override
public void onConnectionFailed(ConnectionResult result) {
Log.d(TAG, "onConnectionFailed: " + result);
}
})
// Request access only to the Wearable API
.addApi(Wearable.API)
.build();
// In onResume
mGoogleApiClient.connect();
25. DroidCon Montréal
2015
23
Syncing Data Items
• A Data Item has a Path and a Payload
• A Path is a unique string that starts with a forward slash. Don't use
underscores.
• A Payload is a byte array limited to 100kb.You must serialize the data
yourself. Flexible, but requires some assembly.
26. DroidCon Montréal
2015
24
Syncing Data Items
• Google recommends using the DataMap class instead. It allows data to
be accessed as key-value pairs. It's used as follows:
27. DroidCon Montréal
2015
25
Receiving Data Changes and Messages
• For background receipt of Data Item changes, implement a subclass of
WearableListenerService and override some of the methods.
• onMessageReceived - for listening for messages.
• onDataChanged - for detecting Data Item changes.
• onPeerConnected
• onPeerDisconnected
• For Foreground receipt of messages and Data Item changes, you can
implement the DataListener and MessageListener interfaces and register with either
the DataApi, or the MessageApi.
28. DroidCon Montréal
2015
26
An Even Better Way
• Use the Courier library written by Denley Bihari!
• It wraps up Wearable.DataApi and Wearable.MessageApi into a simple and clean
delivery service for Android Wear.
• Provides an Annotation processor that simplifies serializing objects, and sending
them as either Data Items or Messages.
29. DroidCon Montréal
2015
27
Courier
• Imagine you want to deliver a message from your phone to a Wear Device
public class Message {
public String mSender;
public String mMessageText;
public long mTimeStamp;
public Message() {
}
public Message(String sender, String messageText, long timeStamp) {
mSender = sender;
mMessageText = messageText;
mTimeStamp = timeStamp;
}
}
import me.denley.courier.Deliverable;
@Deliverable
30. DroidCon Montréal
2015
28
Courier
• Then in the Activity you want to send from, register with Courier to do the
work:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Courier.startReceiving(this);
}
@Override
protected void onDestroy() {
Courier.stopReceiving(this);
super.onDestroy();
}
public void onSaySomethingButtonClicked(View button) {
Courier.deliverMessage(this, "/message", "From Mobile!");
Message message = new Message("Me", getRandomPhrase(), System.currentTimeMillis());
Courier.deliverData(this, "/message/data", message);
}
}
31. DroidCon Montréal
2015
29
Receiving Is Just as Easy
• To Receive data, the code is remarkably similar:
public class ContentActivity extends Activity {
Message mMessage;
@Override
protected void onCreate(Bundle savedInstanceState) {
Courier.startReceiving(this);
}
@Override
protected void onDestroy() {
Courier.stopReceiving(this);
super.onDestroy();
}
private void updateMessage() {
mTextView.setText(mMessage.mMessageText);
}
@ReceiveData("/message/data")
void onMessageChanged(Message message) {
mMessage = message;
updateMessage();
}
}
32. DroidCon Montréal
2015
30
Courier's Packager
• Receiving messages using a WearableListenerService is possible with Courier's
Packager class doing the deserialization. Handles Serializable and classes marked
with Deliverable annotations.
public class ListenerService extends WearableListenerService {
@Override
public void onMessageReceived(MessageEvent messageEvent) {
if (messageEvent.getPath().equals("/message")) {
String message = Packager.unpack(messageEvent.getData(), String.class);
Intent i = new Intent();
i.setAction("org.nsdev.wearableapp.SHOW_NOTIFICATION");
i.putExtra(MyPostNotificationReceiver.CONTENT_KEY, message);
sendBroadcast(i);
}
super.onMessageReceived(messageEvent);
}
}
33. DroidCon Montréal
2015
31
Take a Look At Some Code
• While developing this section of the talk, I wrote some code that demonstrates
the details, and the steps of arriving at the final result can be seen in the
commits.
• Check it out at http://goo.gl/mQsFA1
• Check out the Courier library at: http://goo.gl/qAI4wu
• Thanks to Denley Bihari for permission to talk about his project.
34. DroidCon Montréal
2015
32
Wear UI Classes forYour Toolbelt
• You have a bit of an idea of the mechanics of writing
wear applications, and how to get data to and from
your wear device now, let's look at the UIToolkit.
• Wear UI classes are designed to be easy to use
without a lot of fine touch control.
• The use of cards and swipe gestures is encouraged.
• Applications should be composed of the following
simple building blocks.
42. DroidCon Montréal
2015
34
Round, Square and Butterknife
• You will be needing to use the WatchViewStub to make layouts that work both in
square and round devices.
• The appropriate layout will be chosen automatically.
• Let's go through the layout files and show an example of binding a WatchViewStub
to layouts.
48. DroidCon Montréal
2015
40
Round, Square and Butterknife
ContentActivity.java
@Override
public boolean onTouchEvent(MotionEvent event) {
return mDetector.onTouchEvent(event) || super.onTouchEvent(event);
}
• Let's do something a little interesting with all of this...
• We'll detect a couple more gestures.
49. DroidCon Montréal
2015
41
Adding Interesting Gestures
ContentActivity.java
mDetector = new GestureDetector(ContentActivity.this, new GestureDetector.SimpleOnGestureListener() {
@Override
public void onLongPress(MotionEvent e) {
mDismissOverlayView.show();
}X
});
50. DroidCon Montréal
2015
42
Adding Interesting Gestures
ContentActivity.java
mDetector = new GestureDetector(ContentActivity.this, new GestureDetector.SimpleOnGestureListener() {
@Override
public void onLongPress(MotionEvent e) {
mDismissOverlayView.show();
}X
});
51. DroidCon Montréal
2015
43
Adding Interesting Gestures
ContentActivity.java
mDetector = new GestureDetector(ContentActivity.this, new GestureDetector.SimpleOnGestureListener() {
@Override
public void onLongPress(MotionEvent e) {
mDismissOverlayView.show();
}X
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
Courier.deliverMessage(ContentActivity.this, "/keynote/command", "next");
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
Courier.deliverMessage(ContentActivity.this, "/keynote/command", "previous");
return true;
}
});
52. DroidCon Montréal
2015
44
Mobile App Modifications
MainActivity.java
@ReceiveMessages("/keynote/command")
public void onKeynoteMessage(String message) {
try {
runCommand(message);
} catch (Exception e) {
e.printStackTrace();
}
}
private final OkHttpClient client = new OkHttpClient();
public void runCommand(final String command) throws Exception {
Request request = new Request.Builder()
.get()
.url(String.format("http://%s/json/%s", mServerHost.getText().toString(), command))
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
e.printStackTrace();
}