1Overview

Welcome to the Friendly Chat codelab. In this codelab, you’ll learn how to use the Firebase platform to create Android applications. You will implement a chat client and monitor its performance using Firebase.

What you learn to do

  • Allow users to sign in.
  • Sync data using the Firebase Realtime Database.
  • Receive background messages with Firebase Notifications.
  • Configure an application with Firebase Remote Config.
  • Track application usage flows with Google Analytics for Firebase.
  • Allow users to send invitations to install with Firebase Invites.
  • Display ads with AdMob.
  • Report crashes with Firebase Crash Reporting.
  • Test your app with Firebase Test Lab.

What you need

  • Android Studio version 2.1+.
  • Sample code.
  • A test device with Android 2.3+ and Google Play services 9.8 or later, or an Emulator with Google Play services 9.8 or later
  • Google app version 6.6+ (only needed for testing Firebase App Indexing in Step 9)
  • If using a device, a connection cable.

2Get the sample code

Clone the GitHub repository from the command line:

$ git clone https://github.com/firebase/friendlychat-android

 

8Send Messages

Implement text message sending

In this section, you will add the ability for app users to send text messages. The code snippet below listens for click events on the send button, creates a new FriendlyMessage object with the contents of the message field, and pushes the message to the database. The push() method adds an automatically generated ID to the pushed object’s path. These IDs are sequential which ensures that the new messages will be added to the end of the list.

Update the onClick method of mSendButton in the onCreate method in the MainActivity class. This code is at the bottom of the onCreate method already, update the onClick body to match the code below:

MainActivity.java

mSendButton = (Button) findViewById(R.id.sendButton);
mSendButton.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view) {
       FriendlyMessage friendlyMessage = new 
               FriendlyMessage(mMessageEditText.getText().toString(),
                               mUsername,
                               mPhotoUrl,
                               null /* no image */);
       mFirebaseDatabaseReference.child(MESSAGES_CHILD)
               .push().setValue(friendlyMessage);
       mMessageEditText.setText("");
   }
});

Implement image message sending

In this section, you will add the ability for app users to send image messages. Creating an image message is done with these steps:

  • Select image
  • Handle image selection
  • Write temporary image message to the RTDB
  • Begin to upload selected image
  • Update image message URL to that of the uploaded image, once upload is complete

Select Image

With the following code snippet you will allow the user to select an image from the device’s local storage. Update the onClick method of mAddMessageImageView in the onCreate method in the MainActivity class. This code is at the bottom of the onCreate method already, update the onClick body to match the code below.

MainActivity.java

mAddMessageImageView = (ImageView) findViewById(R.id.addMessageImageView);
mAddMessageImageView.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view) {
       Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
       intent.addCategory(Intent.CATEGORY_OPENABLE);
       intent.setType("image/*");
       startActivityForResult(intent, REQUEST_IMAGE);
   }
});

Handle image selection and write temp message

Once the user has selected an image a call to the MainActivity’s onActivityResult will be fired. This is where you handle the user’s image selection. Using the code snippet below, add the onActivityResult method to MainActivity. In this function you will write a message with a temporary image url to the database indicating the image is being uploaded.

MainActivity.java

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   super.onActivityResult(requestCode, resultCode, data);
   Log.d(TAG, "onActivityResult: requestCode=" + requestCode + ", resultCode=" + resultCode);

   if (requestCode == REQUEST_IMAGE) {
       if (resultCode == RESULT_OK) {
           if (data != null) {
               final Uri uri = data.getData();
               Log.d(TAG, "Uri: " + uri.toString());

               FriendlyMessage tempMessage = new FriendlyMessage(null, mUsername, mPhotoUrl,
                       LOADING_IMAGE_URL);
               mFirebaseDatabaseReference.child(MESSAGES_CHILD).push()
                       .setValue(tempMessage, new DatabaseReference.CompletionListener() {
                           @Override
                           public void onComplete(DatabaseError databaseError,
                                                  DatabaseReference databaseReference) {
                               if (databaseError == null) {
                                   String key = databaseReference.getKey();
                                   StorageReference storageReference =
                                           FirebaseStorage.getInstance()
                                           .getReference(mFirebaseUser.getUid())
                                           .child(key)
                                           .child(uri.getLastPathSegment());

                                   putImageInStorage(storageReference, uri, key);
                               } else {
                                   Log.w(TAG, "Unable to write message to database.",
                                           databaseError.toException());
                               }
                           }
                       });
           }
       }
   }
}

Upload image and update message

Add the method putImageInStorage to MainActivity. It is called in onActivityResult to initiate the upload of the selected image. Once the upload is complete you will update the message to use the appropriate image.

MainActivity.java

private void putImageInStorage(StorageReference storageReference, Uri uri, final String key) {
   storageReference.putFile(uri).addOnCompleteListener(MainActivity.this,
           new OnCompleteListener<UploadTask.TaskSnapshot>() {
               @Override
               public void onComplete(@NonNull Task<UploadTask.TaskSnapshot> task) {
                   if (task.isSuccessful()) {
                       FriendlyMessage friendlyMessage =
                               new FriendlyMessage(null, mUsername, mPhotoUrl,
                                       task.getResult().getMetadata().getDownloadUrl()
                                               .toString());
                       mFirebaseDatabaseReference.child(MESSAGES_CHILD).child(key)
                               .setValue(friendlyMessage);
                   } else {
                       Log.w(TAG, "Image upload task was not successful.",
                               task.getException());
                   }
               }
           });
}

Test Sending Messages

  1. Click the Run button.
  2. Enter a message and hit the send button, the new message should be visible in the app UI and in the Firebase console.
  3. Tap the “+” image to select an image. The new message should be visible first with a placeholder image then once the image upload is complete the selected image. The new message should also be visible in the Firebase console, as an object in the Database and as a blob in Storage.

9Add Messages to the On-device Index

You can use Firebase App Indexing to index personal content to the user’s device. This allows your users to search the Google app and find messages they added in the FriendlyChat application. If your app is installed and enabled with App Indexing, a search query related to personal content takes a user directly into your app, which helps drive re-engagement with your app’s content.

In this section, we configure the application to write messages to the index so that they are discoverable in the Google app.

Add the app indexing dependency

The firebase-appindexing dependency provides the ability to write messages to the on-device index and to log user actions. Add this dependency in your app/build.gradle file.

app/build.gradle

compile 'com.google.firebase:firebase-appindexing:11.8.0'

Add an intent filter

Add an intent filter into your AndroidManifest.xml file to handle incoming links of the form http://friendlychat.firebase.google.com/message/*.

AndroidManifest.xml

<activity android:name="com.google.firebase.codelab.friendlychat.MainActivity">
   <intent-filter>
       <action android:name="android.intent.action.MAIN"/>
       <category android:name="android.intent.category.LAUNCHER"/>
   </intent-filter>
   <intent-filter>
       <action android:name="android.intent.action.VIEW"/>
       <category android:name="android.intent.category.DEFAULT"/>
       <category android:name="android.intent.category.BROWSABLE"/>
       <data
         android:host="friendlychat.firebase.google.com"
         android:scheme="http"
         android:pathPrefix="/message"
/>
   </intent-filter>
</activity>

Add personal content to index

Any time a user sends a message from their device, you can add that message to the on-device index for future discoverability through the Google app.

First, add the following code to create a message to write to the on-device index:

MainActivity.java

private Indexable getMessageIndexable(FriendlyMessage friendlyMessage) {
   PersonBuilder sender = Indexables.personBuilder()
           .setIsSelf(mUsername.equals(friendlyMessage.getName()))
           .setName(friendlyMessage.getName())
           .setUrl(MESSAGE_URL.concat(friendlyMessage.getId() + "/sender"));

   PersonBuilder recipient = Indexables.personBuilder()
           .setName(mUsername)
           .setUrl(MESSAGE_URL.concat(friendlyMessage.getId() + "/recipient"));

   Indexable messageToIndex = Indexables.messageBuilder()
           .setName(friendlyMessage.getText())
           .setUrl(MESSAGE_URL.concat(friendlyMessage.getId()))
           .setSender(sender)
           .setRecipient(recipient)
           .build();

   return messageToIndex;
}

Then, call indexMessage() whenever there is a new message. You can index the FriendlyMessage when the viewHolder for it is populated in the same FirebaseRecyclerAdapter:

MainActivity.java

@Override
protected void onBindViewHolder(final MessageViewHolder viewHolder, FriendlyMessage friendlyMessage, int position) {
   ...

 if (friendlyMessage.getText() != null) {
   // write this message to the on-device index
   FirebaseAppIndex.getInstance()
           .update(getMessageIndexable(friendlyMessage));
 }

}

Note: It helps to add an IntentService that establishes a base index of all messages for you initially. See details in the App Indexing documentation.

Log user actions

Logging user actions in your app helps improve your users’ experience when they search for your app content in the Google app.

Logging user actions on personal content, such as viewing messages, needs to be marked with the upload attribute set to false in the Metadata object of the Action. This ensures user action activity on personal content remains on the device, and will not be uploaded to Google servers.

To log the VIEW_ACTION user action, add the following function to MainActivity.java

MainActivity.java

private Action getMessageViewAction(FriendlyMessage friendlyMessage) {
return new Action.Builder(Action.Builder.VIEW_ACTION)
               .setObject(friendlyMessage.getName(), MESSAGE_URL.concat(friendlyMessage.getId()))
               .setMetadata(new Action.Metadata.Builder().setUpload(false))
               .build();
}

Next, add the FirebaseUserActions.getInstance().end(...) method to indicate the end of logging a user action. You can do this right after you’ve added the new message to the index. Pass getMessageViewAction() from above to this method:

MainActivity.java

...

@Override
protected void onBindViewHolder(final MessageViewHolder viewHolder, FriendlyMessage friendlyMessage, int position) {

...
if (friendlyMessage.getText() != null) {
    // write this message to the on-device index
    FirebaseAppIndex.getInstance()
            .update(getMessageIndexable(friendlyMessage));
}

// log a view action on it
FirebaseUserActions.getInstance().end(getMessageViewAction(friendlyMessage));
}

Test your implementation

  1. Start up the FriendlyChat app and send a new message with the text Hi world!
  2. Go to the Google app, switch to In Apps and search for Hi world.
  3. Confirm that the message can be found in the Google app.
  4. Tap on the result and confirm the FriendlyChat app opens.

Congratulations! You’ve successfully written your app’s content to the on-device index for discoverability in the Google app.

 

10Receive Reengagement Notifications

You can use Firebase Cloud Messaging (FCM) to send notifications to users of your app. In this section we will configure the application to receive reengagement notifications which you can send from Firebase console.

Add FCM dependency

The firebase-messaging dependency provides the ability to send and receive FCM messages. Confirm the existence of this dependency to your app/build.gradle file.

app/build.gradle

compile 'com.google.firebase:firebase-messaging:11.8.0'

Add FCM Services

The RegistrationIntentService class is a background service which is used to request the InstanceID token which identifies the application to the FCM server. It also subscribes to the topic that will be used to send re-engagement notifications (via topic messaging).

The class MyFirebaseMessagingService will be the background service that handles incoming FCM messages.

Update it to extend FirebaseMessagingService which is provided by the firebase-fcm library added earlier. It automatically handles notification messages, which are messages that the server specifies should produce a notification. To handle data messages (which are passed silently to the app rather than automatically creating a notification) you can override the onMessageReceived method from the FirebaseMessagingService base class:

MyFirebaseMessagingService.java

public class MyFirebaseMessagingService extends FirebaseMessagingService {

   private static final String TAG = "MyFMService";

   @Override
   public void onMessageReceived(RemoteMessage remoteMessage) {
       // Handle data payload of FCM messages.
       Log.d(TAG, "FCM Message Id: " + remoteMessage.getMessageId());
       Log.d(TAG, "FCM Notification Message: " +
               remoteMessage.getNotification());
       Log.d(TAG, "FCM Data Message: " + remoteMessage.getData());
   }
}

The class MyFirebaseInstanceIdService will be a service used to handle FCM logic. This service is used to alert the application when a new InstanceID token is generated, and to retrieve the generated token.

Modify it to extend FirebaseInstanceIdService and override the onTokenRefresh method to subscribe to a topic. Use the following code to update the onTokenRefresh method in MyFirebaseInstanceIdService to look like this:

MyFirebaseInstanceIdService.java

public class MyFirebaseInstanceIdService extends FirebaseInstanceIdService {

   private static final String TAG = "MyFirebaseIIDService";
   private static final String FRIENDLY_ENGAGE_TOPIC = "friendly_engage";
  
   /**
    * The Application's current Instance ID token is no longer valid 
    * and thus a new one must be requested.
    */
   @Override
   public void onTokenRefresh() {
       // If you need to handle the generation of a token, initially or
       // after a refresh this is where you should do that.
       String token = FirebaseInstanceId.getInstance().getToken();
       Log.d(TAG, "FCM Token: " + token);

       // Once a token is generated, we subscribe to topic.
       FirebaseMessaging.getInstance()
               .subscribeToTopic(FRIENDLY_ENGAGE_TOPIC);
   }
}

Add service declarations for the MyFirebaseMessagingService and the MyFirebaseInstanceIdService. Add these declarations as children of the application element.

AndroidManifest.xml

<service
   android:name=".MyFirebaseMessagingService"
   android:exported="false">
   <intent-filter>
       <action android:name="com.google.firebase.MESSAGING_EVENT" />
   </intent-filter>
</service>

<service
   android:name=".MyFirebaseInstanceIdService"
   android:exported="false">
   <intent-filter>
       <action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
   </intent-filter>
</service>

That’s it! FCM is all ready to receive messages.

Test Background Notifications

  1. Run the updated application.
  2. Hit the device’s home button (or otherwise send the app to the background).
  3. Use the Composer in the Firebase console to send notifications.
  1. In Firebase console select Notifications from the left navigation bar.
  2. Select Send Your First Message.
  3. Set Message Text to “Friendly Chat?”.
  4. Select the app we connected earlier as the App target.
  5. Click Send Message
  1. Confirm that message is received and notification is displayed on the device. The user should receive a notification that takes them back to the application when tapped.

Hooray! You can re-engage your users easily with FCM. See the documentation for more on FCM.

 

 

11Remotely Configure Friendly Message Length

Firebase Remote Config allows you to remotely configure your application without having to deploy any new code. In this codelab “Friendly Messages” are restricted to a maximum length. By defining this maximum length with Firebase Remote Config rather than hardcoding it in the client, we can update the value over the air through the Firebase console.

Add Config Rules in Firebase console

In the Remote Config section of Firebase console click Add Parameter. Set the parameter key to friendly_msg_length and the parameter value to 10. Make sure to click Publish when you are done.

Add Firebase Remote Config dependency

The firebase-config dependency provides the ability to remotely configure applications. Confirm that the following dependency is added to your app/build.gradle file:

app/build.gradle

compile 'com.google.firebase:firebase-config:11.8.0'

Add a Firebase Remote Config instance variable in the MainActivity class:

MainActivity.java (instance variable)

// Firebase instance variables
private FirebaseRemoteConfig mFirebaseRemoteConfig;

Request and use config

In the MainActivity onCreate method add the following snippet to initialize the FirebaseRemoteConfig and then invoke the fetchConfig method. Add it just above the initialization of the Firebase Realtime Database:

MainActivity.java

// Initialize Firebase Remote Config.
mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();

// Define Firebase Remote Config Settings.
FirebaseRemoteConfigSettings firebaseRemoteConfigSettings =
       new FirebaseRemoteConfigSettings.Builder()
       .setDeveloperModeEnabled(true)
       .build();

// Define default config values. Defaults are used when fetched config values are not
// available. Eg: if an error occurred fetching values from the server.
Map<String, Object> defaultConfigMap = new HashMap<>();
defaultConfigMap.put("friendly_msg_length", 10L);

// Apply config settings and default values.
mFirebaseRemoteConfig.setConfigSettings(firebaseRemoteConfigSettings);
mFirebaseRemoteConfig.setDefaults(defaultConfigMap);

// Fetch remote config.
fetchConfig();

Add fetchConfig and applyRetrievedLengthLimit methods to MainActivity, these methods fetch and activate the retrieved configuration from the Remote Config API. The retrieved configuration determines the max number of characters that a Friendly Message can contain. The default max is 10, set in the FirebaseRemoteConfigSettings object.

MainActivity.java

// Fetch the config to determine the allowed length of messages.
public void fetchConfig() {
   long cacheExpiration = 3600; // 1 hour in seconds
   // If developer mode is enabled reduce cacheExpiration to 0 so that 
   // each fetch goes to the server. This should not be used in release
   // builds.
   if (mFirebaseRemoteConfig.getInfo().getConfigSettings()
           .isDeveloperModeEnabled()) {
       cacheExpiration = 0;
   }
   mFirebaseRemoteConfig.fetch(cacheExpiration)
           .addOnSuccessListener(new OnSuccessListener<Void>() {
               @Override
               public void onSuccess(Void aVoid) {
                   // Make the fetched config available via
                   // FirebaseRemoteConfig get<type> calls.
                   mFirebaseRemoteConfig.activateFetched();
                   applyRetrievedLengthLimit();
               }
           })
           .addOnFailureListener(new OnFailureListener() {
               @Override
               public void onFailure(@NonNull Exception e) {
                   // There has been an error fetching the config
                   Log.w(TAG, "Error fetching config: " + 
                           e.getMessage());
                   applyRetrievedLengthLimit();
               }
           });
}


/**
 * Apply retrieved length limit to edit text field. 
 * This result may be fresh from the server or it may be from cached
 * values.
 */
private void applyRetrievedLengthLimit() {
   Long friendly_msg_length =
           mFirebaseRemoteConfig.getLong("friendly_msg_length");
   mMessageEditText.setFilters(new InputFilter[]{new 
           InputFilter.LengthFilter(friendly_msg_length.intValue())});
   Log.d(TAG, "FML is: " + friendly_msg_length);
}

Add a call to fetchConfig in the onOptionsItemSelected method in MainActivity. The onOptionsItemSelected method should now look like:

MainActivity.java

@Override
public boolean onOptionsItemSelected(MenuItem item) {
   switch (item.getItemId()) {
       case R.id.fresh_config_menu:
           fetchConfig();
           return true;
       case R.id.sign_out_menu:
           mFirebaseAuth.signOut();
           mUsername = ANONYMOUS;
           startActivity(new Intent(this, SignInActivity.class));
           return true;
       default:
           return super.onOptionsItemSelected(item);
   }
}

Test Remote Config

  1. Click the Run button.
  2. Check that the Friendly Message character limit has been set to 10. Update the Remote Config value from 10 to 15 in Firebase console, then publish. From the overflow menu of the app select Fresh Config and confirm that the Friendly Message character limit is now 15.

Congratulations! You now know how to make vital updates to your app without re-releasing it.

 

12Send Install Invites

Firebase App Invites provide a simple way for your users to share your application with their friends through Email or SMS.

Add AppInvite dependency

Confirm that the firebase-appinvites dependency is in your app/build.gradle file:

app/build.gradle

compile 'com.google.android.gms:play-services-appinvite:11.8.0'

Setup GoogleApiClient

Make MainActivity implement the GoogleApiClient.OnConnectionFailedListener interface. This should have already been done so just confirm it has been implemented:

MainActivity.java

public class MainActivity extends AppCompatActivity implements
       GoogleApiClient.OnConnectionFailedListener {

Implement the required onConnectionFailed method. This should have already been done so just confirm it has been implemented:

MainActivity.java

@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
   Log.d(TAG, "onConnectionFailed:" + connectionResult);
}

AppInvites is initiated by calling startActivityForResult, this allows the AppInvites UI to handle the invitation generation then return its completion status to the calling activity via the onActivityResult method. Update the GoogleApiClient initialization to onCreate method in the MainActivity using this:

MainActivity.java

mGoogleApiClient = new GoogleApiClient.Builder(this)
       .enableAutoManage(this, this)
       .addApi(Auth.GOOGLE_SIGN_IN_API)
       .build();

Send invitations

Add the sendInvitation method to MainActivity such that it creates and starts the intent which provides the user the ability to send invitations.

MainActivity.java

private void sendInvitation() {
   Intent intent = new AppInviteInvitation.IntentBuilder(getString(R.string.invitation_title))
           .setMessage(getString(R.string.invitation_message))
           .setCallToActionText(getString(R.string.invitation_cta))
           .build();
   startActivityForResult(intent, REQUEST_INVITE);
}

Handle the Activity result invite callback, which will indicate whether or not the sending of the invites occurred successfully. Update the onActivityResult method in MainActivity so it looks like the snippet below

MainActivity.java

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   super.onActivityResult(requestCode, resultCode, data);
   Log.d(TAG, "onActivityResult: requestCode=" + requestCode + ", resultCode=" + resultCode);

   if (requestCode == REQUEST_IMAGE) {
       if (resultCode == RESULT_OK) {
           if (data != null) {
               final Uri uri = data.getData();
               Log.d(TAG, "Uri: " + uri.toString());

               FriendlyMessage tempMessage = new FriendlyMessage(null, mUsername, mPhotoUrl,
                       LOADING_IMAGE_URL);
               mFirebaseDatabaseReference.child(MESSAGES_CHILD).push()
                       .setValue(tempMessage, new DatabaseReference.CompletionListener() {
                           @Override
                           public void onComplete(DatabaseError databaseError,
                                                  DatabaseReference databaseReference) {
                               if (databaseError == null) {
                                   String key = databaseReference.getKey();
                                   StorageReference storageReference =
                                           FirebaseStorage.getInstance()
                                           .getReference(mFirebaseUser.getUid())
                                           .child(key)
                                           .child(uri.getLastPathSegment());

                                   putImageInStorage(storageReference, uri, key);
                               } else {
                                   Log.w(TAG, "Unable to write message to database.",
                                           databaseError.toException());
                               }
                           }
                       });
           }
       }
   } else if (requestCode == REQUEST_INVITE) {
       if (resultCode == RESULT_OK) {
           // Check how many invitations were sent and log.
           String[] ids = AppInviteInvitation.getInvitationIds(resultCode, data);
           Log.d(TAG, "Invitations sent: " + ids.length);
       } else {
           // Sending failed or it was canceled, show failure message to the user
           Log.d(TAG, "Failed to send invitation.");
       }
   }
}

Add a call to sendInvitation to the onOptionsItemSelected method in MainActivity. The onOptionsItemSelected method should now look like:

MainActivity.java

@Override
public boolean onOptionsItemSelected(MenuItem item) {
   switch (item.getItemId()) {
       case R.id.invite_menu:
           sendInvitation();
           return true;
       case R.id.fresh_config_menu:
           fetchConfig();
           return true;
       case R.id.sign_out_menu:
           mFirebaseAuth.signOut();
           mUsername = ANONYMOUS;
           startActivity(new Intent(this, SignInActivity.class));
           return true;
       default:
           return super.onOptionsItemSelected(item);
   }
}

Test App Invite

  1. Click the Run button.
  2. Tap the menu overflow at the top right of the screen.
  3. Select the Invite option.
  4. You should see the App Invites interface which will allow you to select Email and SMS contacts and send a custom invitation. You must have control of the receiving account to view the invitation once sent.
  5. Tap send and verify that the invitation is sent to the selected contact.
  6. Once your app is in the Play Store the selected contact is taken to app install screen from invite.

You now know how to enable invites. Congrats!

 

13Track User Flows

Google Analytics for Firebase provides a way for you to understand the way users move through your application, where they succeed and where they get stuck. It can also be used to understand the most used parts of your application.

Add Analytics dependency

Confirm the existence of the firebase-analytics dependency your app/build.gradle file:

app/build.gradle

compile 'com.google.firebase:firebase-analytics:11.8.0'

Initialize Analytics

Add a FirebaseAnalytics instance variable to MainActivity:

MainActivity.java

private FirebaseAnalytics mFirebaseAnalytics;

In MainActivity initialize mFirebaseAnalytics by adding the following line to the onCreate method. By initializing Google Analytics for Firebase you will automatically track the lifecycle of your application throughout user sessions without writing any more code.

MainActivity.java

mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);

Send Custom Events

Initializing Google Analytics for Firebase provides some default metrics like app installs and session lifecycle, however you may want to add custom events that help you understand how users interact with your app in specific ways. To send a custom event call mFirebaseAnalytics.logEvent() with the information about the custom event.

In MainActivity log inviting events in the onActivityResult callback. Your onActivityResult callback should handle the REQUEST_INVITE requestCode like the code below. You can see that in each case we send a SHAREevent but with different custom parameters for success and failure.

MainActivity.java

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   super.onActivityResult(requestCode, resultCode, data);
   Log.d(TAG, "onActivityResult: requestCode=" + requestCode + ", resultCode=" + resultCode);

   If (requestCode == REQUEST_IMAGE) {
       // ... 
   } else if (requestCode == REQUEST_INVITE) {
       if (resultCode == RESULT_OK) {
           Bundle payload = new Bundle();
           payload.putString(FirebaseAnalytics.Param.VALUE, "sent");
           mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SHARE,
                   payload);
           // Check how many invitations were sent and log.
           String[] ids = AppInviteInvitation.getInvitationIds(resultCode,
                   data);
           Log.d(TAG, "Invitations sent: " + ids.length);
       } else {
           Bundle payload = new Bundle();
           payload.putString(FirebaseAnalytics.Param.VALUE, "not sent");
           mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SHARE,
                  payload);
           // Sending failed or it was canceled, show failure message to
           // the user
           Log.d(TAG, "Failed to send invitation.");
       }
   }
}

Any events you log to Google Analytics for Firebase will be aggregated, anonymized, and reported in the Firebase console within 24 hours.

14Monetize with Ads

AdMob gives you a way to easily monetize your application, you simply add the AdView placeholder and Google handles the ad delivery for you.

Add AdMob dependency

Confirm the play-services-ads dependency exists in your app/build.gradle file:

app/build.gradle

compile 'com.google.android.gms:play-services-ads:11.8.0'

Add ads namespace

Verify that the ads namespace is in the root RelativeLayout tag in the activity_main.xml file.

activity_main.xml

<RelativeLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   xmlns:ads="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:paddingBottom="@dimen/activity_vertical_margin"
   android:paddingLeft="@dimen/activity_horizontal_margin"
   android:paddingRight="@dimen/activity_horizontal_margin"
   android:paddingTop="@dimen/activity_vertical_margin"
   tools:context="com.google.firebase.codelab.friendlychat.MainActivity">

Add AdView to main layout

Include the view that will contain the ad. In your activity_main.xml file inside the root RelativeLayout add the following AdView tag.

activity_main.xml

<com.google.android.gms.ads.AdView
   android:id="@+id/adView"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_centerHorizontal="true"
   android:layout_alignParentTop="true"
   ads:adSize="BANNER"
   ads:adUnitId="@string/banner_ad_unit_id">
</com.google.android.gms.ads.AdView>

Update RecyclerView element to be laid out below the AdViewRecyclerView should now look like this:

activity_main.xml

<android.support.v7.widget.RecyclerView
   android:id="@+id/messageRecyclerView"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:layout_below="@+id/adView"
   android:layout_above="@+id/linearLayout"/>

Add AdView variable

In the MainActivity add an instance variable that represents the AdView:

MainActivity.java

private AdView mAdView;

Request Ad

In MainActivity in the onCreate method request the ad to be placed in the AdView:

MainActivity.java

mAdView = (AdView) findViewById(R.id.adView);
AdRequest adRequest = new AdRequest.Builder().build();
mAdView.loadAd(adRequest);

Handle lifecycle events

In MainActivity add Activity lifecycle event handling, pausing, resuming and destroying where necessary.

MainActivity.java

@Override
public void onPause() {
   if (mAdView != null) {
       mAdView.pause();
   }
   mFirebaseAdapter.stopListening();
   super.onPause();
}

/** Called when returning to the activity */
@Override
public void onResume() {
   super.onResume();
   mFirebaseAdapter.startListening();
   if (mAdView != null) {
       mAdView.resume();
   }
}

/** Called before the activity is destroyed */
@Override
public void onDestroy() {
   if (mAdView != null) {
       mAdView.destroy();
   }
   super.onDestroy();
}

Test AdMob

  1. Click the Run button.
  2. Verify that Test Ad shows as in the screenshot in step 1.

 

15Report Crashes

Firebase Crashlytics allows your application to report when crashes occur and log the events leading up to the crash.

Add Firebase Crashlytics dependency

Confirm the crashlytics dependency exists in your app/build.gradle file and that the Fabric maven repository dependency is in your project/build.gradle.

app/build.gradle

apply plugin: 'com.android.application'
apply plugin: 'io.fabric'

dependencies {
    // ...
  compile 'com.crashlytics.sdk.android:crashlytics:2.7.1`
}

project/build.gradle

buildscript {
    repositories {
        // ...
        maven {
           url 'https://maven.fabric.io/public'
        }
    }
    dependencies {
        // ...
        classpath 'io.fabric.tools:gradle:1.24.4'
    }
}

Initiate crash

Add a handler for clicks on the Cause Crash menu item. Update the onOptionsItemSelected method in MainActivity so that it now looks like the code below:

MainActivity.java

@Override
public boolean onOptionsItemSelected(MenuItem item) {
   switch (item.getItemId()) {
       case R.id.crash_menu:
           Log.w("Crashlytics", "Crash button clicked");
           causeCrash();
           return true;
       case R.id.invite_menu:
           sendInvitation();
           return true;
       case R.id.fresh_config_menu:
           fetchConfig();
           return true;
       case R.id.sign_out_menu:
           mFirebaseAuth.signOut();
           mUsername = ANONYMOUS;
           startActivity(new Intent(this, SignInActivity.class));
           return true;
              default:
           return super.onOptionsItemSelected(item);
   }
}

Add causeCrash method

In the MainActivity add the causeCrash method below.

MainActivity.java

private void causeCrash() {
   throw new NullPointerException("Fake null pointer exception");
}

Test Firebase Crashlytics

  1. Click the Run button.
  2. Verify that the Cause Crash menu item is available from the overflow menu.
  3. Verify that when Cause Crash is selected the application crashes.
  4. From the logcat verify that the crash report was successfully uploaded.
  • If you do not see logs indicating that the crash report was sent, make sure that the Android Studio logcat filter is set to No Filters.

Now you know how to automatically report crashes in your application and the crash will appear in the Crashlytics dashboard within 5 minutes. Well done!

 

16Test Your App (in the cloud!)

Firebase Test Lab lets you test your app on various types of Android devices across multiple API levels and locales. The best part is that all this testing happens automatically in the cloud without you needing to maintain a collection of test devices.

Add an Espresso instrumentation test case

First we need to add an instrumentation test that we will run on Firebase Test Lab. In the MainActivityEspressoTest.java file, add the following test case method to the class.

MainActivityEspressoTest.java

@Test
    public void verifySignUpButtonDisplayed() {
        onView(ViewMatchers.withId(R.id.sign_in_button)).check(matches(isDisplayed()));
    }

Create a new run configuration for testing

Click app > Edit Configurations…

In the Configurations window, click the plus (+) button, choose Android Tests to create a new test configuration.

Set up the run configuration to run tests on Firebase Test Lab

In the Configurations window, set up the new run configuration as follows:

  1. Name: FriendlyChat Test
  2. Module: app
  3. Test: Class
  4. Class: com.google.firebase.codelab.friendlychat.MainActivityEspressoTest
  5. In Deployment Target Options, on the Target menu, choose Cloud Test Lab Device Matrix. If you are not logged in, click Connect to Google Cloud Platform to connect to Firebase Test Lab.
    Under Cloud project:, click the  icon and select your Google Cloud Platform project from the list.

Configure your test matrix to select test devices

Android Studio includes a Sample configuration that automatically configures a test matrix consisting of several widely-used devices and platform versions. To use the Sample configuration, choose it from the Matrix configuration list and then proceed to step 2 below. To create your own test matrix and select specific devices, platform versions, and locales, complete the optional first step below.

  1. (Optional) If you want to select a custom matrix of test device configurations, perform the following steps:
  1. Click the ellipsis  next to Matrix Configuration to open the Matrix Configurations dialog (below).
  2. Click the Add New Configuration icon  to add a new Matrix, then configure the new Matrix as follows:
  1. Enter a name for your new configuration in the Name field.
  2. Select the devices, Android versions, locales and screen orientations that you want to test your app with. For example, choose any device, a locale, and the latest 2-3 API levels.
  3. Click OK to save your configuration – It should be automatically selected in the “Choose Device” dialog.
  1. Click OK on the Run/Debug Configurations dialog to exit.

Run the new run configuration and verify results

  1. Click on the Run button  to test your app with the selected configuration. The project will build and automatically run the test on each device in the test matrix in the cloud.
  2. After a few minutes, the test results will be displayed in Android Studio. The results of all test runs can also be viewed on the web in the Test Lab section of your project’s Firebase Console, as shown in the figure below.

    Figure: Firebase Test Lab results in Android Studio

    Figure: Firebase Test Lab results in the Firebase Console

17Congratulations!

You have used Firebase to easily build a real-time chat application.

What we’ve covered

  • Firebase Authentication
  • Firebase Realtime Database
  • Firebase Cloud Messaging
  • Firebase Remote Config
  • Google Analytics for Firebase
  • Firebase App Invites
  • Monetizing with AdMob
  • Firebase Crash Reporting
  • Firebase Test Lab for Android

Next Steps

  • Use Firebase in your own Android app.

Learn More