ok glass, develop

If you are a developer looking to get started creating native apps for Google Glass, I hope you find this guide helpful. XE 16 was recently released with an updated version of the GDK, which we will be using to create Weather Report.

Weather Report is a very basic weather app that will display and read aloud the weather for a given city. We will also write some unit tests for components that interact with third-party APIs. The project is open source and available on GitHub.


IDE / Project Setup #

First, we must setup our development environment. I recommend using IntelliJ IDEA 13, which features Gradle integration and many other things that simplify development and deployment of Android / Glass apps.

Let’s create a new Android Module with Gradle:

Screenshot 2014-04-13 18.29.44.png

We’ll want to specify API 19 as the Target SDK and Glass Development Kit Preview as the Compile with:

Screenshot 2014-04-19 12.48.57.png

You will need to install Android SDK Tools, Android SDK Build-tools, and Glass Development Kit Preview from the Android SDK Manager if you haven’t done so already:

Screenshot 2014-04-19 12.51.40.png

At this point, the base scaffold for your project should be setup and look something like this:

Screenshot 2014-04-19 12.55.10.png

Make sure compileSdkVersion in build.gradle is pointing to "Google Inc.:Glass Development Kit Preview:19"

Manifest #

You’ll notice an AndroidManifest.xml file was created for you. Every Android app (and for that matter Glassware) must have one. If you’re familiar with iOS development, it is like Info.plist.

Inside of the <application> directive, we’ll need to add an <activity> tag that represents the main area of focus when our Glassware is launched. But first, let’s create the actual activity.

Activity #

An Activity is a single, focused thing that the user can do. It is responsible for rendering the window where your UI will go. For iOS devs, you can think of it as the UIViewController of the Android universe.

An Activity can start another Activity using an Intent and pass along data via a Bundle. In fact, the “ok glass” launcher that comes with Glass uses intents to launch apps, passing to them bundles containing data like voice transcribed text.

Let’s create the main activity for Weather Report:

Screenshot 2014-04-19 13.19.00.png

onCreate is where most initialization happens, like setting the view and attaching event handlers.

We’ll add these later. For now, let’s go back to the manifest file and declare this activity:

Screenshot 2014-04-19 13.31.32.png

Inside of <activity>, we need to specify an <intent-filter> so that Glass knows that it can launch our app from the “ok glass” launcher. New to the latest version of GDK is the requirement to include <uses-permission> in the manifest when using custom voice commands. Since the voice trigger “weather report” is not one of the supported commands, we need to request the DEVELOPMENT permission.

All we need to specify inside of xml/voice_trigger.xml is a <trigger> containing the string representing the actual command, which in this case also contains a prompt for the city name:

Screenshot 2014-04-19 14.15.11.png
Screenshot 2014-04-19 14.15.46.png

Test Drive #

At this point, we should have just enough components wired up to test drive our app. Make sure your Glass device is connected to your computer and run (Run > Run ‘app’):

Screenshot 2014-04-19 14.17.59.png
Screenshot 2014-04-19 14.18.44.png


Open Weather Map API #

To actually provide relevant weather data, our app will need to make a request to some service, like Open Weather Map API. We will be using the forecast endpoint that takes a city name and provides a JSON object containing information like description and temperature:

http://api.openweathermap.org/data/2.1/forecast/city?q=new york

A common method to make HTTP requests on Android is using AndroidHttpClient inside of an AsyncTask. AsyncTask provides an abstraction over dealing with Threads and Handlers, but still requires a fair amount of structure around what you want to do in the background thread and then how you use that data on the main UI thread. Then there’s the manual parsing of the JSON response.

For iOS dev, there exists a library called AFNetworking that makes getting JSON data a matter of providing a url and a callback, with the background task and JSON parsing all handled for you.

Fortunately, the Android team at Google recently released a similar library for Android, Volley. There isn’t much documentation available, but the code and javadoc is fairly self-explanatory. To be able to use Volley, we’ll need to add it as a dependency to our build.gradle file:

dependencies {
  compile 'com.mcxiaoke.volley:library:1.0.+'
}

com.mcxiaoke.volley is a mirror of the library that resides on Maven Central Repository, allowing us to easily fetch it without worrying about copying jar files around.

OpenWeatherAPI.java #

Let’s create the class that will leverage Volley to make a request to the Open Weather Map API and provide a callback with data that our MainActivity cares about to render the view:

public class OpenWeatherAPI {

To keep things simple, we’ll structure our API url so that we just need to append the city name to it:

  public static final String URL = "http://api.openweathermap.org/data/2.1/forecast/city?units=imperial&q=";

Let’s create a static nested class to represent the chunk of data our MainActivity is interested in from the whole response that comes back:

  public static class WeatherData {
    public final String description;
    public final int temperature;

    public WeatherData(String description, int temperature) {
      this.description = description;
      this.temperature = temperature;
    }
  }

Due to the asynchronous nature of Volley making requests on a background thread, we cannot call OpenWeatherAPI from MainActivity and expect a return value with the WeatherData. To get the data, we’ll need to use a callback:

  public interface Callback {
    void onWeatherData(WeatherData weatherData);
  }

By creating an interface, we could use an anonymous class and treat it as a function, pretty neat eh?

Finally, we create the getWeatherData static method that makes the actual JSON request and passes an instance of WeatherData to the provided Callback.

Due to the narrow margins of this website, please refer to the following link for the code: OpenWeatherAPI.java

RequestQueue is a type of manager Volley uses to queue up requests and dispatch them at the opportune moment. By calling queue.add, we add the JsonObjectRequest to the request queue and make the actual request.

JsonObjectRequest makes it super easy by providing an onResponse callback with the response body parsed as a JSONObject.

All that’s left to do is parse the values WeatherData cares about and pass it along to the callback.

That wasn’t so bad.


Hooking it up #

In MainActivity, we call getWeatherData, passing to it the city name from the voice prompt and rendering a Card with the weather data.

Text To Speech #

Text To Speech is super simple in Android. All we have to do is initialize the engine and have it speak.

Not so fast #

We forgot about one last thing, the permission to access the internet. In the AndroidManifest.xml file, add the following:

<uses-permission android:name="android.permission.INTERNET"/>


Unit Tests #

Screenshot 2014-04-20 11.38.43.png

Gradle knows that src/androidTest should contain test code. It also supports dependencies that should only be installed when running tests. We will take advantage of that to use Hamcrest, a library that simplifies assertions with a robust set of matchers to test for object types, empty strings, and more:

dependencies {
  androidTestCompile 'org.hamcrest:hamcrest-all:1.3'
}

We will be unit testing the OpenWeatherAPI#getWeatherData method. To do so, we subclass AndroidTestCase which provides us with a mock Context object in addition to everything JUnit’s TestCase has.

Mock Callback #

When we call OpenWeatherAPI.getWeatherData, a request will be made on the background thread. Since the test suite is running on the main thread, our tests will finish before our request ever has a chance to come back. We will need to block on the main thread until the request comes back and the callback is executed. To do that, we can use synchronized statements:

class MockCallback implements OpenWeatherAPI.Callback {
  public OpenWeatherAPI.WeatherData weatherData;

  @Override
  public void onWeatherData(OpenWeatherAPI.WeatherData weatherData) {
    this.weatherData = weatherData;

    synchronized (this) {
      notifyAll();
    }
  }
}

In our testGetWeatherData method, we can now block on the mock callback object waiting for the notifyAll() call to take place before our assertions run:

MockCallback callback = new MockCallback();
OpenWeatherAPI.getWeatherData("new york", mQueue, callback);

synchronized (callback) {
  callback.wait(10000);
}

assertThat("WeatherData description", callback.weatherData.description, not(isEmptyOrNullString()));

assertThat("WeatherData temperature", callback.weatherData.temperature, instanceOf(Integer.class));

assertThat("WeatherData temperature", callback.weatherData.temperature, notNullValue());

Running the test suite #

To run the unit test suite, you’ll need to connect your Google Glass device to the computer and execute the connectedAndroidTest Gradle task.

You can also use the gradlew CLI tool:

$ ./gradlew connectedAndroidTest


That’s all Folks! #

Hopefully you now have a better understanding of how to write native Glassware using Android and GDK APIs as well as using build tools like Gradle. There are more topics to explore in the Glass universe like Live Cards, Touch Gestures, and Sensors. The platform is fresh and the possibilities endless.

Me #

GitHub-Mark.png
twitter.png

Resources #

 
1
Kudos
 
1
Kudos

Now read this

MFWalkthrough: A Container View Controller for iOS

The iOS Developer Library has an excellent article on creating custom container view controllers, so I won’t go too much into detail on that. This post will mainly focus on MFWalkthrough, an iOS library I created to simplify the process... Continue →