Pimp my Wear (part 4)

In Colonizing Wearables, after a first attempt of running the existing handheld version of the Books application on a smartwatch, we ended up creating a simple Android Wear module that displays the number of available book entries. This has been done not only to discover more about Android Wear, but because a handheld app should not be just ported to a wearable device. During our journey, we have introduced few of the new UI components offered by the wear support library. Among them, WatchViewStub has allowed us to handle different screen shapes, figuring out at runtime the type of display. We also had a chance to understand the reason why and how the wearable module has to be included into the handheld build. Therefore, the goal of this part is to evolve the previous module into something that can be easily used by user and that tries to follow as much as possible the android wear design patterns.

Being on a device with a small touchable area and constrained by limited resources, require us to give particular attention to the features that we want to expose through the wearable app. We definitely do not want to provide all the available functionalities in the handheld app but just the ones those can be important for the user and his context.
Our books application offers an important feature that is the book suggestion and we can try to enrich that experience through Android Wear. Just for the sake of this tutorial we are going to choose a solution that is definitely not the optimal one for the end users but the one that can give us, as developers, the opportunity to play with the new Android Wear UI elements and some related concepts. At the end of this chapter there will be some considerations about possible improvements and changes those should be taken into account when developing a real Android Wear app for a smartwatch.

Looking at the android wear design patterns, there are multiple ways to display content and allow the user to interact with it. In our case, one option might be just showing the suggested books in a WearableListView as simple entries. This solution is really straightforward to implement having worked before with RecyclerView. At the same time it does not offer any flexibility if we want to start adding actions and enrich each book entry.
Instead, we need to provide a different UI that makes easy to add elements to the book entries and at the same time that keeps simple the navigation between different elements. The navigation pattern of the 2D Picker might be what we are looking for. If we follow the guidelines, the user will be then able to swipe vertically between different books suggestions and horizontally between the available actions. The UI ViewGroup that allows us to implement the expected 2D Picker is called GridViewPager and it is able to bind an adapter that can map different columns on different rows like in a matrix. If a user is navigating horizontally and at a certain point he decides to swipe vertically, the next position will be the first column of the next or previous available entry. This can be consider as a kind of shortcut that tries to create always a context for the user.

2D Picker preferred navigation

Keeping in mind that we do not want to “invade” the user experience with tons of different elements, the maximum number for the rows should be 5 in order to avoid confusion and the navigation between different entries should be always first-vertical-then-horizontal. Following this pattern and those small rules should grant us a consistent experience and should allow the user to perceive something that he should be already used to know.

Hence, in our wearable application we would probably like to display the book suggestions in different rows, allowing the user to swipe right to access more info and actions.

Looking at the first two screens, we might wonder how to create the card effect that wraps our content. Luckily, the Android Wear Support library offers a CardFragment component that can easily allow us to obtain the expected result. Apart from displaying the content in an expandable card, this element takes care by default of distinguishing between round and rect screens. This feature becomes really handy when we are not sure if the content will fit well in the screen. Another peculiarity of this fragment extension is that it provides two static factory methods those allow us to create, in just one line, simple cards made of a title, a description and eventually an icon.

Having the cursor pointing at the current entry, if we have a look at the BooksActivity, our first page can be created through the following lines:

final String title = mCursor.getString(mCursor.getColumnIndex(Book.TITLE));
final String author = mCursor.getString(mCursor.getColumnIndex(Book.AUTHOR));
return CardFragment.create(title, author);

If we want something just different or more complex, but still providing a card style, we can extend CardFragment and override the onCreateContentView(…) method. Therefore, we can apply our own layout and custom behaviour.

public class MyCustomCardFragment extends CardFragment {
  @Override
  protected View onCreateContentView(...) {
    View view = inflater.inflate(R.layout.my_custom_card, parent, false);
     …
    return view;
  }   
}

Moving forward to the two pages(“Add notes” and “Buy book”), we can notice that there is no card based approach. There is what looks like an action button that when clicked performs an operation and possibly gives a feedback to the user. If we have a look at the Android Wear Design patterns again, when we provide an action that can apply important changes, we should expose to the user a way to abort it, delaying its execution. Again, the support library for wearable devices comes in play and helps us offering a new view that is called DelayedConfirmationView. This component, extending CircledImageView, provides us a circular countdown timer that can be listened for changes and allows the user to cancel the action before a custom timeout. Setting a DelayConfirmationListener ensure that we can complete the action without having to ask directly the user to confirm if he is 100% sure of what he is trying to do. At the same time, the application can be notified if the user suddenly changes his mind.

Following what we have just discovered, our BuyBookFragment page looks like:

class BuyBookFragment ... implements DelayedConfirmationListener {

  private DelayedConfirmationView mConfirmationView;

  public View onCreateView(...) {
    View v = inflater.inflate(R.layout.fragment_buy_book, container, false);
    mConfirmationView = (DelayedConfirmationView) v.findViewById(...);
    return v;
  }

  public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    view.setOnClickListener(new View.OnClickListener() {
      public void onClick(View v) {
        mConfirmationView.setTotalTimeMs(2000L); 
        mConfirmationView.start(); 
      }
    });
    mConfirmationView.setListener(this);
  }

  public void onTimerFinished(View view) {
    // Timer completed => execute purchase
  }

  public void onTimerSelected(View view) {
    // The user has cancelled the action execution, nothing to do
  }
}

The AddNoteFragment implements a similar logic for the delayed confirmation. The only difference between the two is related to when the timer has to be started. In the case of adding a note, the user has to pronounce a valid text input to make the timer start, otherwise the current UI will not change.

The voice input is obtained from the result of a speech Intent as a list of strings generally sorted in descending order by the confidence in matching what the user has said.

Intent i = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
i.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, LANGUAGE_MODEL_FREE_FORM);
i.putExtra(RecognizerIntent.EXTRA_PROMPT, getString(R.string.what_notes));
startActivityForResult(i, REQUEST_NOTES_CODE);

public void onActivityResult(int requestCode, int resultCode, Intent i) {
  List<String> l = i.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
}

An important aspect that characterizes the wear experience is the constant feedback that has to be presented to the user for every action he performs. We are going to see how an experience can be perceived as truncated when we will try to add some shortcuts without notifying back to the user in our app settings. In the early days of Android a lot of those feedbacks were shown as toasts, alert dialogs or status bar notifications depending on the particular case. Even if Android Wear does an heavy use of notifications, a dialog or a toast are not even part of the current design guidelines. Instead, the wear support library bundles a component that comes with the name of ConfirmationActivity. This activity specialization allows developers to show the result of an action as an animation consistent across different apps. At the time of the writing, there are three different types that can be displayed:

  • Success
  • Failure
  • Open on Phone

In order to use the ConfirmationActivity we have to declare it in the AndroidManifest.xml of the wearable module previously created:

<activity android:name="android.support.wearable.activity.ConfirmationActivity"/>

Hence, we can display a confirmation feedback supplying the type of animation that we want to be performed and an optional message:

Intent i = new Intent(activity, ConfirmationActivity.class);
i.putExtra(ConfirmationActivity.EXTRA_ANIMATION_TYPE, 
           ConfirmationActivity.SUCCESS_ANIMATION);
i.putExtra(ConfirmationActivity.EXTRA_MESSAGE,  “Book purchased”);
startActivity(intent);
activity.finish();

One curious fact about the animation is that for every frame the animation is using drawables with a different bitmap, wasting a lot of physical space. This behaviour might be subject to change in the future, maybe taking advantage of AnimatedVectorDrawable.

If the suggestions do not satisfy the user expectations, we can do one step further and provide, as a final resort, also some secondary actions in a settings page. Android Wear does not provide any kind of pre-built-in fragment for that but we can try to build our own with the tools and the knowledge acquired till now. In our case we just want to expose some bulk actions those allow to buy/sell all the elements or restore the default database state. In order to achieve that, we can take advantage of WearableListView displaying the actions as simple list items. Do not be confused by the name of this component because it is a little bit misleading: it refers to ListView while it is effectively based on the more flexible and extensible RecyclerView. This specialization targets in particular wearable devices displaying the content only vertically from top to bottom and snapping to the closest element when the current scroll is almost finished.

Another thing that we have to keep in mind is that the screen might have a round or rect shape. In “Colonizing Wearables” we had a look at WatchViewStub and at how we have to supply two different layouts in order to handle different screens. Here, we want to take another approach based on a component called BoxInsetLayout that allows to wrap the children selected edges(left, top, right, bottom or all) into a shape-aware box. This means that all the elements cannot exceed the box constrained by the considered edges and the type of the screen where the app is running.

BoxInsetLayoutInstead of having one layout for the WatchViewStub and two for the different screen shapes, the fragment_settings.xml layout contains everything we need:

<android.support.wearable.view.BoxInsetLayout 
  ...
  tools:deviceIds="wear">

  <FrameLayout
    ...
    app:layout_box="left|bottom|right" >

    <android.support.wearable.view.WearableListView ... />
  </FrameLayout>
</android.support.wearable.view.BoxInsetLayout>

Hence, the SettingsFragment.java can get the reference to the WearableListView without having to wait in order to let the parent figure out the screen shape like in the case of WatchViewStub. Because our fragment is going to be added to the GridViewPager, we need to ensure that WearableListView is always granted the right of handling the touch events, preventing the pager to steal them. Again, the wearable support library comes to the aid of us directly in the component that we are populating with our entries:

mWearableListView.setGreedyTouchMode(true); 

One hidden feature of WearableListView, that distinguish it from RecyclerView and that allows to make cool UI effects, is the capability of making the items aware of their proximity to the center of their parent. In our case we want to highlight the current selected element applying a different alpha(Chet Haase could probably kill us for not having used just a gray color to obtain the same effect) level and translating the view on its x axis by a defined distance like in the following screenshot:

Settings

Settings page

All we have to do is to make our item views implement the OnCenterProximityListener interface and let the ViewGroup figure out if it has or not to notify the proximity changes.

Our WearableListTextView, specialization of TextView, will look like:

class … implements WearableListView.OnCenterProximityListener {
  public void onCenterPosition(boolean animate) {
    …
  }

  public void onNonCenterPosition(boolean animate) {
    …
  }
}

A video that shows how the entire app works can be found on YouTube here. It is possible to notice that there is no feedback when the user selects one of the option in the settings page. This seems like truncating the flow and does not give the possibility to understand if the action has completed successfully or not. In a real scenario, it would definitely be a better experience to use the ConfirmationActivity to provide an instant feedback, maybe followed by a notification in the ContextStream that shows if the long running operation successfully completed or not.

In this chapter we had a chance to make some experiments with the wearable support library and to understand how many of the concepts used on an handheld device are slightly changing on Android Wear. When we develop for Android Wear we should always consider the wearable device, in our case a smartwatch, as a companion for the user that should try to observe and supply the content that the user needs at one specific time or in one particular situation. Having said that, the wearable app that we have just built is not probably the best option because it requires the user to have an active interaction. In a real scenario, it would be nicer and definitely more engaging having suggestions based on particular user behaviours and bundled with maps indications to the closest/cheapest shop.

Next time we are going to have a look at how synchronize content between the wearable and the handheld device.
Personally, I think that our journey is getting more and more interesting…

If you have found this tutorial useful, please share it on Google+, Facebook or wherever you prefer!

Thanks for having survived till now 😛
Simone

Advertisements

About alchemiasoft

Android Developer at Bloomberg LP. Algorithms and performance passionate. Curious. Eager to learn.

Posted on February 5, 2015, in Android, Development and tagged , , , , , , , , , , , , , , , , . Bookmark the permalink. 4 Comments.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: