How to use Firebase Dynamic links and Invites on Android

Hey guys! Today, I am going to tell you how you can use deep linking technology to provide best user experience across different platforms. If you have not heard of deep linking before, then this is the post you should read. I will try to cover as much as I learnt about it in the past few days.

What is deep linking?

In simpler terms, It is a technology that closes the gap between multiple platforms. For example, Suppose your friend has emailed or shared some youtube video to you. When you are on web platform, you click on that url and it will simply navigate you to the youtube video. But in mobile, If you have configured deep linking in your app, When you click on the link, It will ask you to open that link either in chrome, youtube or some other app that is also configured to handle youtube video urls.

device-2016-07-09-191744

That is how youtube web and mobile are linked to provider better user experience as Youtube app will handle video streaming much better than your mobile browser.

Why deep linking? Is it just used to link multi platform apps?

Well no, there’s much more to it. There are multiple use cases. Some of them are:

  • Converting web users to app users: When a user shares a link from your web app, you can generate deep link for it. So when the user open that link on different device, they will automatically redirected to your app.
  • To increase user experience: Suppose the user was browsing your app and decides to email that link to itself. If user has your app installed, it will automatically be navigated to that content that he was previously browsing.
  • For promotional offers and campaigns: You can send promotional offers via emails or SMS. So When they click on link, they will directly redirected to the specified screen in your app.

Deep Linking Problems

Android provides great documentation to enable deep linking in your app. But there are many problems associated with it:

Deep Linking only works when your app is already installed in your device.

This is major problem. You can write your own code for handling such case like redirecting user to the play store to install your app. But even you managed to do that, there is case, when user installs your app and open it, the context (e.g. It was youtube video link) associated with the link got lost and it will just open the home screen. User has to re-navigate to that video which is bad user experience.

So, What should I use? Is there any tool or service to manage that problem?

Yes, there are many third party services like branch.io, appsflyer etc. and they are great. Recently, Google has launched various products under Firebase and it includes deep linking tool too. You can easily manage App indexing, deep linking via Firebase Dynamic links and if your app has some invitation functionality, Firebase Invites is awesome.

Firebase Dynamic links handles all the deep linking problems whether your app is installed or not. If your app is not already installed, It can redirect the user to play store or you can specify a web page to redirect to.

Firebase invites, by using dynamic links, can send invites to multiple users via email or sms. While sending out invite, you can customize the content and link according to your needs.

Enough of talk, I want implementation guide

Although, Firebase has provide enough documentation  and code samples to get started with their products, I will try to show simple steps to integrate in your easily.

View Project on Github

Setting up Firebase project

  1. Login with your Google account and go to Firebase Console.
  2. Click on Create Project. Enter your project name and country.
    Screenshot at Jul 02 12-00-12
  3. And Done, your Firebase project has been created and you have been redirected to the dashboard. There are lot of things there. You can integrate a lot of things from this one firebase project into your app.
  4. Click on Home icon on the left tab and you will see a screen to add your app (Android or iOS). I am going to select Android.
  5. It now shows to complete three steps. Step one is to enter your package name and SHA1 fingerprint. Enter your android application package name and run below commands to generate SHA1 fingerprint:
    On Mac:
    keytool -exportcert -list -v \
    -alias androiddebugkey -keystore ~/.android/debug.keystore

    On Windows:

    keytool -exportcert -list -v \
    -alias androiddebugkey -keystore %USERPROFILE%\.android\debug.keystore

    Again, as you see, this is using default key store provided by android studio. For production purposes, you need to use your release key store.
    Screenshot at Jun 25 18-27-50
    Screenshot at Jul 02 12-12-54

  6. Click on Add app. Step two is download and copy your google-services.json file and copy into app directory of your Android project. This json file is a configuration file for your firebase project. Your project needs a plugin to read it.
    Screenshot at Jul 02 12-40-21
  7. Open your root folder build.gradle file and include this dependency:
    classpath 'com.google.gms:google-services:3.0.0'

    Your root folder build.gradle should look like this:

    // Top-level build file where you can add configuration options common to all sub-projects/modules.
    
    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:2.1.2'
            classpath 'com.google.gms:google-services:3.0.0'
    
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        }
    }
    
    allprojects {
        repositories {
            jcenter()
        }
    }
    
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
  8. Then, open app level build.gradle file, add this dependency:
    dependencies {
      // ...
     
        compile 'com.google.firebase:firebase-messaging:9.2.0'
     }
    Also, apply above plugin at the bottom of the file:
    apply plugin: 'com.google.gms.google-services'

    Your app level build.gradle should look like this:

    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 24
        buildToolsVersion "23.0.3"
    
        defaultConfig {
            applicationId "vi.firebasenotifcations"
            minSdkVersion 15
            targetSdkVersion 23
            versionCode 1
            versionName "1.0"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        testCompile 'junit:junit:4.12'
        compile 'com.android.support:appcompat-v7:24.0.0'
        compile 'com.google.firebase:firebase-invites:9.2.0'
    }
    
    // ADD THIS AT THE BOTTOM
    apply plugin: 'com.google.gms.google-services'

    Note: If you are getting error “Failed to resolve com.google.firebase:firebase-messaging:9.2.0”, then you need to update your Google Play services version (as of now 31) and Google Repository version (as of now 29) to the latest or else, you can replace 9.2.0 to 9.0.2 (not recommended).
    Screenshot at Jul 02 12-58-40

    Integrate Firebase Invites

  9. Create a LauncherActivity which contains a button to invite friends. When the user clicks on the button, we are going to launch AppInviteActivity included in Firebase SDK which will list all contacts to send the invitation. We just have to put initial configuration which includes message, deep link, custom image. When the user selects and send invites to the contacts, It will return invitation ids of all invites in onActivityResult() method.
     /**
         * User has clicked the 'Invite' button, launch the invitation UI with the proper
         * title, message, and deep link
         */
        private void onInviteClicked() {
            Intent intent = new AppInviteInvitation.IntentBuilder(getString(R.string.invitation_title))
                    .setMessage(getString(R.string.invitation_message))
                    .setDeepLink(Uri.parse(getString(R.string.invitation_deep_link)))
                    .setCustomImage(Uri.parse(getString(R.string.invitation_custom_image)))
                    .setCallToActionText(getString(R.string.invitation_cta))
                    .build();
            startActivityForResult(intent, REQUEST_INVITE);
        }
    
        @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_INVITE) {
                if (resultCode == RESULT_OK) {
                    // Get the invitation IDs of all sent messages
                    String[] ids = AppInviteInvitation.getInvitationIds(resultCode, data);
                    for (String id : ids) {
                        Log.d(TAG, "onActivityResult: sent invitation " + id);
                    }
                } else {
                    // Sending failed or it was canceled, show failure message to the user
                    // [START_EXCLUDE]
                    showMessage(getString(R.string.send_failed));
                    // [END_EXCLUDE]
                }
            }
        }
    
  10. Now, we will create DeepLinkingActivity and configure it in the manifest file so that it can handle deep links.
    <activity android:name=".DeepLinkActivity">
        <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="example.com" android:scheme="http"/>
            <data android:host="example.com" android:scheme="https"/>
        </intent-filter>
    </activity>
    

    By creating an intent filter for this activity, we are specifying VIEW action when user visits any link which uses scheme http or https and host example.com. When we visit any link (e.g. http://example.com/offer/five_dollar_offer) It will automatically open DeepLinkActivity.

  11. Also in DeepLinkActivity, we will handle this intent in onStart() method to check if this activity is opened by click on invite url.
      // [START deep_link_on_start]
        @Override
        protected void onStart() {
            super.onStart();
    
            // Check if the intent contains an AppInvite and then process the referral information.
            Intent intent = getIntent();
            if (AppInviteReferral.hasReferral(intent)) {
                processReferralIntent(intent);
            }
        }
        // [END deep_link_on_start]
    
        // [START process_referral_intent]
        private void processReferralIntent(Intent intent) {
            // Extract referral information from the intent
            String invitationId = AppInviteReferral.getInvitationId(intent);
            String deepLink = AppInviteReferral.getDeepLink(intent);
    
            // Display referral information
            // [START_EXCLUDE]
            Log.d(TAG, "Found Referral: " + invitationId + ":" + deepLink);
            ((TextView) findViewById(R.id.deep_link_text))
                    .setText(getString(R.string.deep_link_fmt, deepLink));
            ((TextView) findViewById(R.id.invitation_id_text))
                    .setText(getString(R.string.invitation_id_fmt, invitationId));
            // [END_EXCLUDE]
        }
        // [END process_referral_intent]
    

    By checking the intent, we can easily know about the invitation Id and deep link url of the invite.

  12. Now, suppose If you want to check if this app is launched by deep link, you can do this by creating an GoogleApiClient which uses AppInvite API and call getInvitation() method to get result in callback method. I have created DeepLinkManager class for managing all the things.
    public class DeepLinkManager implements GoogleApiClient.OnConnectionFailedListener,
            ResultCallback&amp;amp;amp;lt;AppInviteInvitationResult&amp;amp;amp;gt;{
    
        private static final String TAG = DeepLinkManager.class.getSimpleName();
        private final GoogleApiClient mGoogleApiClient;
        private final FragmentActivity context;
    
        private DeepLinkListener deepLinkListener;
    
        public interface DeepLinkListener{
            void onConnectionError(String errorMessage);
        }
    
        public DeepLinkManager(FragmentActivity activity, DeepLinkListener linkListener) {
            mGoogleApiClient = new GoogleApiClient.Builder(activity)
                    .addApi(AppInvite.API)
                    .enableAutoManage(activity, this)
                    .build();
    
            this.context = activity;
            this.deepLinkListener = linkListener;
        }
    
    
        @Override
        public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
            Log.d(TAG, "onConnectionFailed:" + connectionResult);
            deepLinkListener.onConnectionError("Google Play Services error!");
        }
    
        public void checkForInvites(boolean autoLaunchDeepLink)
        {
            // Check for App Invite invitations and launch deep-link activity if possible.
            // Requires that an Activity is registered in AndroidManifest.xml to handle
            // deep-link URLs.
    
            AppInvite.AppInviteApi.getInvitation(mGoogleApiClient, context, autoLaunchDeepLink)
                    .setResultCallback(this);
        }
    
        @Override
        public void onResult(@NonNull AppInviteInvitationResult result) {
            Log.d(TAG, "getInvitation:onResult:" + result.getStatus());
            if (result.getStatus().isSuccess()) {
                // Extract information from the intent
                Intent intent = result.getInvitationIntent();
                String deepLink = AppInviteReferral.getDeepLink(intent);
                String invitationId = AppInviteReferral.getInvitationId(intent);
    
                // Because autoLaunchDeepLink = true we don't have to do anything
                // here, but we could set that to false and manually choose
                // an Activity to launch to handle the deep link here.
                // ...
            }
        }
    }
    

    Also, while calling getInvitation() method, if autoLaunchDeeplink is true, then it will automatically launch that activity which is configured for that particular link in the manifest file. And if it is false, you can get result in onResult() method and handle all the code to launch an activity manually.

  13. Lets see the flow:
    device-2016-07-10-185610        device-2016-07-10-185707

    device-2016-07-10-185804
             device-2016-07-10-185821
    Here, you can see I received an email from other id that I have used to send invite. When we click on install, It will directly open DeepLinkActivity as it was configured for that link.
    but If suppose we are on web or app is not installed, and we click that link, we can navigated to the play store to download that app.
    Screenshot at Jul 10 19-04-39
    Since our app is not listed on play store, It is showing this. But you can look at the url to know that this link was configured to open link for our app.
  14. You can also generate dynamic link from firebase console by clicking on Dynamic links on the left tab.
    Screenshot at Jul 10 19-08-46
  15. You can also generate your own custom deep link. There’s whole lot of documentation for that. As you see, When i created above link, It has generated this long url.
    Screenshot at Jul 10 19-11-20Firebase Dynamic links is a awesome products by Google. You can do a lot with it. If there’s something that needed to learn. Please tell me in comments. Thanks for reading this post. View Project on Github
  • gubendran lakshmanan

    Hi ArunSharma,

    Thanks for the details step. It was nice. Is there anyway you have this detail steps for iOS integration ?

    Thanks,