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.
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:
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.
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.
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
privatevoid putImageInStorage(StorageReference storageReference,Uri uri,finalString key){
storageReference.putFile(uri).addOnCompleteListener(MainActivity.this,newOnCompleteListener<UploadTask.TaskSnapshot>(){@Overridepublicvoid onComplete(@NonNullTask<UploadTask.TaskSnapshot> task){if(task.isSuccessful()){FriendlyMessage friendlyMessage =newFriendlyMessage(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
Click the Run button.
Enter a message and hit the send button, the new message should be visible in the app UI and in the Firebase console.
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.
9. Add 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.
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
@Overrideprotectedvoid onBindViewHolder(finalMessageViewHolder viewHolder,FriendlyMessage friendlyMessage,int position){...if(friendlyMessage.getText()!=null){// write this message to the on-device indexFirebaseAppIndex.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
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
...@Overrideprotectedvoid onBindViewHolder(finalMessageViewHolder viewHolder,FriendlyMessage friendlyMessage,int position){...if(friendlyMessage.getText()!=null){// write this message to the on-device indexFirebaseAppIndex.getInstance().update(getMessageIndexable(friendlyMessage));}// log a view action on itFirebaseUserActions.getInstance().end(getMessageViewAction(friendlyMessage));}
Test your implementation
Start up the FriendlyChat app and send a new message with the text Hi world!
Go to the Google app, switch to In Apps and search for Hi world.
Confirm that the message can be found in the Google app.
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.
10. Receive 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.
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
publicclassMyFirebaseMessagingServiceextendsFirebaseMessagingService{privatestaticfinalString TAG ="MyFMService";@Overridepublicvoid 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
publicclassMyFirebaseInstanceIdServiceextendsFirebaseInstanceIdService{privatestaticfinalString TAG ="MyFirebaseIIDService";privatestaticfinalString FRIENDLY_ENGAGE_TOPIC ="friendly_engage";/**
* The Application's current Instance ID token is no longer valid
* and thus a new one must be requested.
*/@Overridepublicvoid 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.
Hit the device’s home button (or otherwise send the app to the background).
Use the Composer in the Firebase console to send notifications.
In Firebase console select Notifications from the left navigation bar.
Select Send Your First Message.
Set Message Text to “Friendly Chat?”.
Select the app we connected earlier as the App target.
Click Send Message
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.
11. Remotely 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:
In the MainActivityonCreate 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 =newFirebaseRemoteConfigSettings.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 =newHashMap<>();
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.publicvoid 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(newOnSuccessListener<Void>(){@Overridepublicvoid onSuccess(Void aVoid){// Make the fetched config available via// FirebaseRemoteConfig get<type> calls.
mFirebaseRemoteConfig.activateFetched();
applyRetrievedLengthLimit();}}).addOnFailureListener(newOnFailureListener(){@Overridepublicvoid onFailure(@NonNullException e){// There has been an error fetching the configLog.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.
*/privatevoid applyRetrievedLengthLimit(){Long friendly_msg_length =
mFirebaseRemoteConfig.getLong("friendly_msg_length");
mMessageEditText.setFilters(newInputFilter[]{newInputFilter.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:
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.
12. Send 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:
Make MainActivity implement the GoogleApiClient.OnConnectionFailedListener interface. This should have already been done so just confirm it has been implemented:
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:
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
@Overrideprotectedvoid 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){finalUri uri = data.getData();Log.d(TAG,"Uri: "+ uri.toString());FriendlyMessage tempMessage =newFriendlyMessage(null, mUsername, mPhotoUrl,
LOADING_IMAGE_URL);
mFirebaseDatabaseReference.child(MESSAGES_CHILD).push().setValue(tempMessage,newDatabaseReference.CompletionListener(){@Overridepublicvoid 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());}}});}}}elseif(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 userLog.d(TAG,"Failed to send invitation.");}}}
Add a call to sendInvitation to the onOptionsItemSelected method in MainActivity. The onOptionsItemSelected method should now look like:
Tap the menu overflow at the top right of the screen.
Select the Invite option.
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.
Tap send and verify that the invitation is sent to the selected contact.
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!
13. Track 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:
Add a FirebaseAnalytics instance variable to MainActivity:
MainActivity.java
privateFirebaseAnalytics 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.
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
@Overrideprotectedvoid onActivityResult(int requestCode,int resultCode,Intent data){super.onActivityResult(requestCode, resultCode, data);Log.d(TAG,"onActivityResult: requestCode="+ requestCode +", resultCode="+ resultCode);If(requestCode == REQUEST_IMAGE){// ... }elseif(requestCode == REQUEST_INVITE){if(resultCode == RESULT_OK){Bundle payload =newBundle();
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 =newBundle();
payload.putString(FirebaseAnalytics.Param.VALUE,"not sent");
mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SHARE,
payload);// Sending failed or it was canceled, show failure message to// the userLog.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.
14. Monetize 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:
In MainActivity add Activity lifecycle event handling, pausing, resuming and destroying where necessary.
MainActivity.java
@Overridepublicvoid onPause(){if(mAdView !=null){
mAdView.pause();}
mFirebaseAdapter.stopListening();super.onPause();}/** Called when returning to the activity */@Overridepublicvoid onResume(){super.onResume();
mFirebaseAdapter.startListening();if(mAdView !=null){
mAdView.resume();}}/** Called before the activity is destroyed */@Overridepublicvoid onDestroy(){if(mAdView !=null){
mAdView.destroy();}super.onDestroy();}
Test AdMob
Click the Run button.
Verify that Test Ad shows as in the screenshot in step 1.
15. Report 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.
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:
Verify that the Cause Crash menu item is available from the overflow menu.
Verify that when Cause Crash is selected the application crashes.
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!
16. Test 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.
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.
(Optional) If you want to select a custom matrix of test device configurations, perform the following steps:
Click the ellipsis next to Matrix Configuration to open the Matrix Configurations dialog (below).
Click the Add New Configuration icon to add a new Matrix, then configure the new Matrix as follows:
Enter a name for your new configuration in the Name field.
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.
Click OK to save your configuration – It should be automatically selected in the “Choose Device” dialog.
Click OK on the Run/Debug Configurations dialog to exit.
Run the new run configuration and verify results
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.
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
17. Congratulations!
You have used Firebase to easily build a real-time chat application.
Task 1. Extend SQLiteOpenHelper to create and populate a database
1.1 Create a skeleton WordListOpenHelper class
1.2. Add database constants to WordListOpenHelper
1.3. Build the SQL query and code to create the database
1.4 Create the database in onCreate of the MainActivity
1.5 Add data to the database
Task 2. Create a data model for a single word
2.1. Create a data model for your word data
Task 3. Implement the query() method in WordListOpenHelper
3.1. Implement the query() method
3.2. The onUpgrade method
Task 4. Display data in the RecyclerView
4.1. Update WordListAdapter to display WordItems
Task 5. Add new words to the database
5.1. Write the insert() method
5.2. Get the word to insert from the user and update the database
5.3. Implement getItemCount()
Task 6. Delete words from the database
6.1. Write the delete() method
6.2. Add a click handler to DELETE button
Task 7. Update words in the database
7.1. Write the update() method
7.2. Add a click listener to the EDIT button
7.3. Add updating to onActivityResult
7.4. Design and error considerations
The methods you wrote to add, update and delete entries in the database all assume that their input is valid. This is acceptable for sample code because the purpose of this sample code is to teach you the basic functionality of a SQLite database, and so not every edge case is considered, not every value is tested, and everybody is assumed to be well behaved. If this were a production app, you would have greater security considerations, and content would need to be tested for validity until you know it is not malicious.
In a production app, you must catch specific exceptions and handle them appropriately.
You tested the correct functioning of the app by running it. For a production app with real data, you will need more thorough testing, for example, using unit and interface testing.
For this practical, you created the the database schema/tables from the SQLiteOpenHelper class. This is sufficient for a simple example, like this one. For a more complex app, it is a better practice to separate the schema definitions from the rest of the code in a helper class that cannot be instantiated. You will learn how to do that in the chapter on content providers.
As mentioned above, some database operations can be lengthy and should be done on a background thread. Use AsyncTask for operations that take a long time. Use loaders to load large amounts of data.
Recent Comments