What you'll learn

What you'll need

Download all the sample code to your computer using EITHER of the following methods:

$ git clone https://github.com/googlecodelabs/app-indexing.git

From Android Studio, import the app-indexing-start directory () from the downloaded code:

File > Open > .../app-indexing/app-indexing-start

You should now have the app-indexing-start project open in Android Studio.

  1. Open the Firebase Console and click Create New Project and name it RecipeApp.
  2. Click Add Firebase to your Android App.
  3. Enter com.recipe_app as your Package Name. This is the same name as the applicationId in the app-level build.gradle file. You can leave the SHA1 field empty.
  4. Click Add and download a configuration file that contains all the necessary Firebase metadata for your app. Copy the google-services.json file into the app/ directory in your project. Follow the rest of the directions in the console.
  5. The google-services plugin uses the google-services.json file to configure the application to use Firebase. In the app/ directory of your project, check your build.gradle file to confirm that the following line is already added at the end:
apply plugin: 'com.google.gms.google-services'
  1. Select Sync Project with Gradle Files from the Android Studio tool bar. This ensures all dependencies are available to the app and syncs your project with gradle files.

Add the App Indexing gradle target to the build.gradle file found in the app/ directory. Confirm the following line already appears in the build.gradle file:

build.gradle

dependencies {
   ... 
   compile 'com.google.firebase:firebase-appindexing:10.0.0'
   ...
}

Now that you have configured the project with Firebase, run the sample app by clicking Run in the Android Studio toolbar. The app should launch on your device and display a splash page for the RecipeApp similar to the one below:

The app doesn't do much for now, but soon you will add support for incoming links.

Frequently Asked Questions

First, you will add support in the app for URLs associated with the public content of its website. The recipe-app.com website and Android app are already structured in a way that supports a common set of links that both platforms can use and open.

You want the app to open on the appropriate recipe screen for any links directed toward recipe-app.com. To do this, add support to handle URLs that match links of the form http://recipe-app.com/recipe/* to open up in the app. Do this by adding an <intent-filter> to the AndroidManifest.xml file like so:

AndroidManifest.xml

...
<activity
   android:name=".client.RecipeActivity"
   android:label="@string/app_name"
   android:exported="true"
   android:launchMode="singleTop"
   android:theme="@android:style/Theme.Holo.NoActionBar">
   <intent-filter android:label="@string/app_name" android:autoVerify="true">
       <action android:name="android.intent.action.VIEW" />
       <category android:name="android.intent.category.DEFAULT" />
       <category android:name="android.intent.category.BROWSABLE" />
       <!-- Accepts URIs that begin with "http://recipe-app.com/recipe" -->
       <data android:scheme="http"
           android:host="recipe-app.com"
           android:pathPrefix="/recipe" />
   </intent-filter>
</activity>
...

Verify that the app can correctly handle HTTP links: first launch the app from Android Studio, and then type the following into your terminal:

$ adb shell am start -a android.intent.action.VIEW \
-d "http://recipe-app.com/recipe/pierogi-poutine" com.recipe_app

You should now see a recipe for Pierogi Poutine on your device.

You just made it possible for the app to provide the right app screen for any corresponding recipe-app.com URL passed to it.

To read more about supporting HTTP links in your app, read Support links to your app content.

At this point, you can use the API to write personal content to the on-device index. The Recipe App has a feature that allows users to comment and leave a note on each recipe for future use. By writing user notes to the index, the API makes it possible for users of the app to search through their personal recipe notes in the Google App.

First, write an IntentService that periodically updates the index with notes for the most up-to-date information. Add the following code to the AppIndexingService.java file in the project:

AppIndexingService.java

public class AppIndexingService extends IntentService {
...
   @Override
   protected void onHandleIntent(Intent intent) {
       ArrayList<Indexable> indexableNotes = new ArrayList<>();

       for (Recipe recipe : getAllRecipes()) {
           Note note = recipe.getNote();
           if (note != null) {
               Indexable noteToIndex = Indexables.noteDigitalDocumentBuilder()
                       .setName(recipe.getTitle() + " Note")
                       .setText(note.getText())
                       .setUrl(recipe.getNoteUrl())
                       .build();

               indexableNotes.add(noteToIndex);
           }
       }

       if (indexableNotes.size() > 0) {
           Indexable[] notesArr = new Indexable[indexableNotes.size()];
           notesArr = indexableNotes.toArray(notesArr);

           // batch insert indexable notes into index
           FirebaseAppIndex.getInstance().update(notesArr);
       }
   }
...
}

Allow Google Play Services to call this IntentService periodically by adding the following to your AndroidManifest.xml file:

AndroidManifest.xml

<service android:name=".client.AppIndexingService"
  android:exported="true"
  android:permission="com.google.android.gms.permission.APPINDEXING">
   <intent-filter>
       <action android:name="com.google.firebase.appindexing.UPDATE_INDEX" />
   </intent-filter>
</service>

Any time a user adds a note to a recipe, the application should add that text to the on-device index so that it shows up in the Google app. The API has many handy builders that you can use for writing personal content to the index. For an added note on the recipe, use noteDigitalDocumentBuilder. Add the following method to index a note in RecipeActivity.java, and uncomment a line in the displayNoteDialog() method where indexNote()is called:

RecipeActivity.java

private void indexNote() {
   Note note = mRecipe.getNote();
   Indexable noteToIndex = Indexables.noteDigitalDocumentBuilder()
           .setName(mRecipe.getTitle() + " Note")
           .setText(note.getText())
           .setUrl(mRecipe.getNoteUrl())
           .build();

   Task<Void> task = FirebaseAppIndex.getInstance().update(noteToIndex);
   task.addOnSuccessListener(new OnSuccessListener<Void>() {
       @Override
       public void onSuccess(Void aVoid) {
           Log.d(TAG, "App Indexing API: Successfully added note to index");
       }
   });

   task.addOnFailureListener(new OnFailureListener() {
       @Override
       public void onFailure(@NonNull Exception exception) {
           Log.e(TAG, "App Indexing API: Failed to add note to index. " + exception
                   .getMessage());
       }
   });
}

Now, when a user adds a note to the recipe app, it is also added to the index.

Any time the user deletes a note from the recipe, the application should also remove the note from the on-device index so that it no longer appears in personal search results. You must remove items from the index by their associated URL. This is the same URL used for setUrl when initially adding the note to the index.

The following .remove() function in RecipeActivity.java, shows how a note is removed from the index. Confirm that RecipeActivity.java contains the following lines:

RecipeActivity.java

...
// Deletes or removes the corresponding notes from index.
String noteUrl = mRecipe.getNoteUrl();
FirebaseAppIndex.getInstance().remove(noteUrl);

Any time a user searches in the Google app, you want relevant results from your app's content to be available as autocomplete. To make this possible, log user actions on the public content after adding it to the index.

The FirebaseUserActions.getInstance().start() function registers that the user started viewing a particular recipe. Add the start() call below to the onStart function of the RecipeActivity.java class. After that, add the getRecipeViewAction() method.

RecipeActivity.java

@Override
public void onStart() {
   super.onStart();
   if (recipe != null) {
      indexRecipe();
      FirebaseUserActions.getInstance().start(getRecipeViewAction());
   }
}

private void indexRecipe() {
    Indexable recipeToIndex = new Indexable.Builder()
           .setName(mRecipe.getTitle())
           .setUrl(mRecipe.getRecipeUrl())
           .setImage(mRecipe.getPhoto())
           .setDescription(mRecipe.getDescription())
           .build();

    FirebaseAppIndex.getInstance().update(recipeToIndex);
}

private Action getRecipeViewAction() {
   return Actions.newView(mRecipe.getTitle(),mRecipe.getRecipeUrl());
}

When a user has finished viewing a recipe, relay the completed user action with the following end() method in the RecipeActivity.java file:

RecipeActivity.java

@Override
public void onStop() {
   if (recipe != null) {
       FirebaseUserActions.getInstance().end(getRecipeViewAction());
   }
   super.onStop();
}

This method indicates the end of a user action and allows the API to measure activity duration, such as time spent on a recipe.

You will now also log user actions on personal content— that is, the action of adding a note to a recipe.

For public content, user actions data is uploaded to Google and the builder enables this capability by default. For personal content, you should explicitly pass in the false argument to the setUpload()function of the action builder to keep user actions data on the device.

Add the following function to the RecipeActivity.java file, passing false as an argument to the setUpload() function:

RecipeActivity.java

private Action getNoteCommentAction() {
   return new Action.Builder(Action.Builder.COMMENT_ACTION)
           .setObject(mRecipe.getTitle() + " Note", mRecipe.getNoteUrl())
           .setMetadata(new Action.Metadata.Builder().setUpload(false))
           .build();
}

Add the following code right after addNoteDialog.show() is called:

RecipeActivity.java

...
addNoteDialog.show();
FirebaseUserActions.getInstance().start(getNoteCommentAction());

Be sure to also uncomment corresponding .end() calls in displayNoteDialog when a user finishes adding a note, marked with TODO (developer): comments in the code.

Let's test to see recipes and notes from RecipeApp were correctly written to the index and appear in the Google app. You can find more ways to test your implementation in our developer documentation.

Handle Incoming Links

  1. Build and run the sample app by clicking Run in the Android Studio toolbar
  2. Type the following into your terminal window:
$ adb shell am start -a android.intent.action.VIEW \
-d "http://recipe-app.com/recipe/pierogi-poutine" com.recipe_app
  1. If the Pierogi Poutine recipe displays on your screen, links directed to recipe-app.com are correctly being displayed in the RecipeApp instead.

Public Content and User Actions

  1. Using the Google app, search for Pierogi Poutine. As you begin to type, autocompletions will begin to appear.
  2. Verify that the app can be discovered through the Google app with autocomplete as shown above.
  3. Leave the app open for the next step.

Personal Content and User Actions

Add personal content

  1. While on the Pierogi Poutine recipe, tap the note toggle button on the screen.
  2. Type This recipe was delicious. Next time, half it.
  3. Hit Add, which should close the dialog box and save the note in the app and additionally, add it to the index.
  4. Using the Google app, go to the In Apps tab. Search for Pierogi Poutine Note. You should see the RecipeApp listed as a result.
  5. Tap the Pierogi Poutine Note result and make sure that it links to the correct recipe view in the sample app.
  6. Leave the app open for the next step.

Remove personal content

  1. While on the Pierogi Poutine recipe, tap the note toggle button on the screen. The note you wrote previously should still be there.
  2. Tap Delete to remove the note from the recipe and the on-device index.
  3. Confirm that the note is no longer discoverable from the Google app in the In Apps view.

The app is now ready to show content in the Google app powered by the Firebase App Indexing API!

What we've covered

If you would like to find out more about Firebase Android App Indexing, please see the full developer documentation.

You can post questions and find answers on Stack Overflow under the firebase-app-indexing tags.