Check-In apps are so last year. In this code lab, we’ll build a simple Check-Out app to find the hottest places people are leaving. We’ll use the Google Places API for Android to manage nearby locations and we’ll use Firebase to store and synchronise data across devices in real time.

What you’ll learn

What you’ll need

How will you use this tutorial?

Skim readRead it and complete the exercisesCheck out the final code and run the app

How would rate your experience with building Android apps?

NoviceIntermediateProficient

Have you ever used the Google Places or Firebase APIs before?

NeitherYep, just the Google Places APIYep, just FirebaseYep, both

Let’s create our new app from scratch.

1. Open Android Studio and choose New Project, either from the Quick Start dialog or from the File menu.

2. Name your application Check Out and enter google.io as your company domain. Leave the Project Location un-touched. Click Next.

3. We’ll leave the default settings for device targets, that is just Phone and Tablet with a Minimum SDK of API 19 (KitKat). Click Next.

4. Our app will be driven by a map, so on the activity selection screen, choose Google Maps Activity and click Next.

5. Set the title to Check Outs! and leave the rest of the settings as they are. Click Finish.

6. Wait for the initial build to complete. This is where Android Studio downloads the dependencies and performs an initial compilation so that you can get started straight away.

Before we can start using the Places API, we need to get an API key and set the key up in our app.

When Android Studio finished setting up your project, it should have opened a file with some instructions. We’re going to follow those instructions.

1. If it’s not already open in front of you, open google_maps_api.xml. It will be in the project under app/res/values.

2. The contents will look something like this.

google_maps_api.xml

<resources>
   <!--
   TODO: Before you run your application, you need a Google Maps API key.

   To get one, follow this link, follow the directions and press "Create" at the end:

https://console.developers.google.com/flows/enableapi?apiid=maps_android_backend&keyType=CLIENT_SIDE_ANDROID&r=27:8F:79:CC:AF:73:B8:C6:2F:7E:04:7E:F8:E8:4A:A3:07:85:37:14%3Bio.google.checkout

   You can also add your credentials to an existing key, using this line:
   27:8F:79:CC:AF:73:B8:C6:2F:7E:04:7E:F8:E8:4A:A3:07:85:37:14;io.google.checkout

   Once you have your key (it starts with "AIza"), replace the "google_maps_key"
   string in this file.
   -->
   <string name="google_maps_key" translatable="false" templateMergeStrategy="preserve">
       YOUR_KEY_HERE
   </string>
</resources>

3. In the comment, there is a link you need to copy and paste into your browser. Log in if you are prompted.

4. Follow the prompts on the page to create a new project or re-use one you’ve already set up.

5. When prompted to “Create an Android key and configure allowed Android applications” you should see that the box is already pre-filled with the information listed in google_maps_api.xml. Just click Create.

6. Now select APIs from the left-hand navigation (underneath APIs & auth).

7. On the APIs page, click on the Google Places API for Android link. It will be under the Google Maps APIs.

8. Now click Enable API. You’re now ready to use the API!

9. To get the API key we need, click on the Credentials link on the left-hand navigation.

10. Copy the text next to API key (it will be a long string starting with AIza…), head back into Android Studio and replace the text YOUR_KEY_HERE with the key you just copied.

11. One last step! Open up AndroidManifest.xml in Android Studio (it will be under  app/manifests) and find this code:

AndroidManifest.xml

<meta-data
   android:name="com.google.android.maps.v2.API_KEY"
   android:value="@string/google_maps_key" />

And replace it with this code:

AndroidManifest.xml

<meta-data
   android:name="com.google.android.geo.API_KEY"
   android:value="@string/google_maps_key" />

This uses the new name for the key, so that the Places API can find it.

Firebase is an “Instant Data API” that we’ll be using to get real-time data updates in our app.

1. Load up https://www.firebase.com/signup/ in your web browser

2. Enter your email, create a super-secret password and click Create My Account.

3. On the welcome page, find the My First App dialog. You will want to take note of your Firebase URL, it’s the text that ends in .firebaseIO.com.

Before we make any changes, let’s run the app as it is.

1. Ensure your device is plugged into your machine’s USB port.

a. If you’re here at Google I/O, then your machine and device should be set up and ready to go, if not then you can follow the instructions on setting up your device for debugging.

2. In Android Studio, press the Run button and wait for the project to build.

3. Select your device from the list and click OK.

4. Check out the map on your device!

Let’s add the Check Out button to our app, even though it won’t do much yet.

1. We’ll add the button first. Open up the layout file for the app, activity_maps.xml, found under app/res/layout.

2. To make the button appear on top of the map, we’ll wrap everything in a FrameLayout and insert a <Button> above the <fragment> holding the map. Replace the contents of the whole file with this code. Note that the button has an onClick attribute - we’ll define that function in a few steps.

activity_maps.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_height="match_parent"
             android:layout_width="match_parent">

   <Button android:id="@+id/checkout_button"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:text="@string/check_out"
           android:onClick="checkOut"/>

   <fragment xmlns:android="http://schemas.android.com/apk/res/android"
             xmlns:tools="http://schemas.android.com/tools"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:id="@+id/map"
             tools:context=".MapsActivity"
             android:name="com.google.android.gms.maps.SupportMapFragment"/>

</FrameLayout>

3. Android Studio will complain that the button text @string/check_out is not defined. Fix the error by clicking on the error and pausing for a second, a red bulb should appear in the left gutter , click it and choose Create string value resource ‘check_out’.

4. Set the resource value to Check Out! (this will be the text on our button) and click OK.

We’ll add a few subtle but nice visual effects to our map: some padding so that the button can “float” over the map, a blue dot indicating the user’s location and we’ll zoom the map to the user’s location.

1. Open the MapsActivity class (found in  app/java/io/google/checkout)

2. We’re going to use the new getMapAsync(..) method in the Android Maps API, so let’s replace MapsActivity completely. Paste this code into the file, overwriting the entire contents.

MapsActivity.java

package io.google.checkout;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;

public class MapsActivity extends FragmentActivity implements OnMapReadyCallback {

   private GoogleMap mMap;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_maps);

       // Set up Google Maps
       SupportMapFragment mapFragment = (SupportMapFragment)
               getSupportFragmentManager().findFragmentById(R.id.map);
       mapFragment.getMapAsync(this);
   }

   /**
    * Map setup. This is called when the GoogleMap is available to manipulate.
    */
   @Override
   public void onMapReady(GoogleMap googleMap) {
       mMap = googleMap;
   }
}

3. To set the padding on the top of the map to make room for the button, add the following code to the onMapReady method. This will ensure that we set the padding correctly once the button has been drawn on-screen, as the map may be ready before the button is drawn.

MapsActivity.java

// Pad the map controls to make room for the button - note that the button may not have
// been laid out yet.
final Button button = (Button) findViewById(R.id.checkout_button);
button.getViewTreeObserver().addOnGlobalLayoutListener(
        new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mMap.setPadding(0, button.getHeight(), 0, 0);
            }
        }
);

4. To make sure everything we’ve drawn on the map is visible, we’ll use a LatLngBounds Builder and a helper method to update it and animate the camera when we get new points.

a. Add this line at the top, under the private GoogleMap mMap line.

MapsActivity.java

private LatLngBounds.Builder mBounds = new LatLngBounds.Builder();

b. Now add this helper method in the class:

MapsActivity.java

private void addPointToViewPort(LatLng newPoint) {
    mBounds.include(newPoint);
    mMap.animateCamera(CameraUpdateFactory.newLatLngBounds(mBounds.build(),
            findViewById(R.id.checkout_button).getHeight()));
}

c. Now we can add this code to onMapReady to enable the user’s location and update the viewport when we have a GPS lock.

MapsActivity.java

mMap.setMyLocationEnabled(true);
mMap.setOnMyLocationChangeListener(new GoogleMap.OnMyLocationChangeListener() {
    @Override
    public void onMyLocationChange(Location location) {
        LatLng ll = new LatLng(location.getLatitude(), location.getLongitude());
        addPointToViewPort(ll);
        // we only want to grab the location once, to allow the user to pan and zoom freely.
        mMap.setOnMyLocationChangeListener(null);
    }
});

5. Now is a good time to hit the Run button and see how the app looks on your phone.

Now let’s wire up the button to the Place Picker!

1. The Places API uses the GoogleApiClient in Google Play Services to manage API connections, so we’ll need to set that up.

a. Add this field at the top of the file, below private GoogleMap mMap.

MapsActivity.java

private GoogleApiClient mGoogleApiClient;

b. And add this code in your onCreate() method.

MapsActivity.java

// Set up the API client for Places API
mGoogleApiClient = new GoogleApiClient.Builder(this)
   .addApi(Places.GEO_DATA_API)
   .build();
mGoogleApiClient.connect();

2. Recall that in our layout, we set the button’s on-click handler to be a method called checkOut. Let’s write that now.

a. We want to fire an intent and receive a result, so we’ll need a result code. Insert this constant declaration with the other fields at the top of the file (e.g. above private GoogleMap mMap)

MapsActivity.java

private static final int REQUEST_PLACE_PICKER = 1;

b. Here’s the code for checkOut(). It creates an intent using PlacePicker.IntentBuilder, starts it and handles two important exceptions that users may encounter. Note that the GooglePlayServicesRepairableException is recoverable, so we attempt to do so.

MapsActivity.java

/**
* Prompt the user to check out of their location. Called when the "Check Out!" button
* is clicked.
*/
public void checkOut(View view) {
   try {
       PlacePicker.IntentBuilder intentBuilder = new PlacePicker.IntentBuilder();
       Intent intent = intentBuilder.build(this);
       startActivityForResult(intent, REQUEST_PLACE_PICKER);
   } catch (GooglePlayServicesRepairableException e) {
       GoogleApiAvailability.getInstance().getErrorDialog(this, e.getConnectionStatusCode(),
               REQUEST_PLACE_PICKER);
   } catch (GooglePlayServicesNotAvailableException e) {
       Toast.makeText(this, "Please install Google Play Services!", Toast.LENGTH_LONG).show();
   }
}

3. Once the user has chosen a place, onActivityResult will be called, so we need to implement that now. This code checks that the intent was successful and uses PlacePicker.getPlace() to obtain the chosen Place.

MapsActivity.java

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   if (requestCode == REQUEST_PLACE_PICKER) {
       if (resultCode == Activity.RESULT_OK) {
           Place place = PlacePicker.getPlace(data, this);
       } else if (resultCode == PlacePicker.RESULT_ERROR) {
           Toast.makeText(this, "Places API failure! Check that the API is enabled for your key",
                   Toast.LENGTH_LONG).show();
       }
   } else {
       super.onActivityResult(requestCode, resultCode, data);
   }
}

4. Now press the Run button and when the app loads, tap the Check Out button to use the Place Picker. It won’t do anything with the result yet, but we’ll fix that next.

While the Place Picker is beautiful, this app doesn’t do much. We need to add storage!

1. First add the dependency on Firebase to the app’s  build.gradle file.

a. Add the final compile line from the snippet below to your app module’s build.gradle file (not the CheckOut project’s build.gradle).

build.gradle

dependencies {
   compile fileTree(dir: 'libs', include: ['*.jar'])
   compile 'com.android.support:appcompat-v7:22.1.1'
   compile 'com.google.android.gms:play-services:7.3.0'

   compile 'com.firebase:firebase-client-android:2.2.4'
}

b. Add this block to the same file, within the android { .. } block.

build.gradle

packagingOptions {
   exclude 'META-INF/LICENSE'
   exclude 'META-INF/NOTICE'
}

c. Click the Gradle sync button to download the Firebase library to your local project.

2. Make sure you have your Firebase URL that we set up in the Enable Firebase step. We need to add it as a constant (e.g. below private static final int REQUEST_PLACE_PICKER = 1).

a. Paste in the snippet below and replace the value of FIREBASE_URL with your Firebase URL. i.e. the code below would be correct if your URL was https://ferg-burger-420.firebaseio.com/

b. In order to keep our check-out properly separated from any other data we might store later, we will store everything under a single node, that we’re arbitrarily calling checkouts.

MapsActivity.java

private static final String FIREBASE_URL = "https://ferg-burger-420.firebaseio.com/";
private static final String FIREBASE_ROOT_NODE = "checkouts";

c. Add a class member to hold the reference to our Firebase connection, like so. Once again, put this with the other members (e.g. private GoogleMap mMap).

MapsActivity.java

private Firebase mFirebase;

3. When our Activity is created, we need to open our connection to Firebase. Add the connection code in the onCreate method. Here we are also setting up a ChildEventListener to capture incoming data events.

MapsActivity.java

// Set up Firebase
Firebase.setAndroidContext(this);
mFirebase = new Firebase(FIREBASE_URL);
mFirebase.child(FIREBASE_ROOT_NODE).addChildEventListener(this);

4. To write the selected places to Firebase, we need to modify onActivityResult to create the database entry when it retrieves the Place.  The complete method should look like this.

a. This creates or updates a record in Firebase with a key matching the Place ID and with a value of { time: 14024376459 }, where the timestamp is set to the server’s current time.

MapsActivity.java

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   if (requestCode == REQUEST_PLACE_PICKER) {
       if (resultCode == Activity.RESULT_OK) {
           Place place = PlacePicker.getPlace(data, this);

           Map<String, Object> checkoutData = new HashMap<>();
           checkoutData.put("time", ServerValue.TIMESTAMP);

           mFirebase.child(FIREBASE_ROOT_NODE).child(place.getId()).setValue(checkoutData);

       } else if (resultCode == PlacePicker.RESULT_ERROR) {
           Toast.makeText(this, "Places API failure! Check the API is enabled for your key",
                   Toast.LENGTH_LONG).show();
       }
   } else {
       super.onActivityResult(requestCode, resultCode, data);
   }
}

5. To handle data changes, we need to implement the missing ChildEventListener methods.

a. Add ChildEventListener to the list of interfaces that the MapsActivity class implements.

MapsActivity.java

public class MapsActivity extends FragmentActivity 
        implements OnMapReadyCallback, ChildEventListener {

b. Android Studio will indicate that there’s an error here, press Alt-Enter, choose Implement methods and click OK.

c. For now we are only interested in data being added to our map (that is, when a new CheckOut occurs). In the onChildAdded method, we want to look up the Place ID and add a marker to the map.

MapsActivity.java

@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
   String placeId = dataSnapshot.getKey();
   if (placeId != null) {
       Places.GeoDataApi
               .getPlaceById(mGoogleApiClient, placeId)
               .setResultCallback(new ResultCallback<PlaceBuffer>() {
                          @Override
                          public void onResult(PlaceBuffer places) {
                              LatLng location = places.get(0).getLatLng();
                              addPointToViewPort(location);
                              mMap.addMarker(new MarkerOptions().position(location));
                              places.release();
                          }
                      }
               );
   }
}

6. Now press the Run button and once the app has loaded:

a. Tap the Check Out button to use the Place Picker.

b. Choose a place nearby.

c. Watch in awe as the place is added to your map!

For this step you will need a second device. Plug it into your machine and press the Run button, installing the app to the new device.

Make sure you can see both devices - this is important!

1. On one device, make sure the map is visible.

2. On the other device, tap Check Out and choose a place.

3. Watch the devices to see how both screens update simultaneously with the new data!

You now have a technical skeleton for a hot new Check Out app. Now add some more user-focused features and hunt down some VC funding!

What we've covered

Next Steps