ZOFTINO.COM android and web dev tutorials

Firebase Remote Config Android Example & A/B Testing

If you want to change your android app’s theme or text, you will have to release new version of your android app incorporating the theme or text changes that you want to make. This way of releasing config changes requires user to upgrade your app to have the new version of app on user’s device and see the changes.

But with Firebase remote config, configuration changes can be pushed to installed app without users having to reinstall it.

In this post, you can learn how Firebase remote works with an example, create A/B test experiment and testing A/B test on test device.

Firebase Remote Config Project Set Up

Before going into the details of how Firebase remote config works, you need to set up android project. To do that, first you need to create a Firebase project in Firebase console, and then add your android project to your firebase project by selecting the project, clicking add-Firebase to android project, and following steps. In the end of the flow, you will generate google-services.json file which you need to download and save in android project app folder.

After project has been added to Firebase, you need to install Firebase SDK in your android project. To do that, you need to add 'com.google.gms.google-services' plug-in to the bottom of app level gradle build file and add gsm library 'com.google.gms:google-services:3.1.0' to classpath in project level gradle build file.

Then add firebase remote config library to project by adding below entry to build.gradle file.

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

Now we will go into details of how Firebase remote config works.

Defining Default Values for Parameters in XML

First you need to identify parameters such as text colors, UI controls colors, and text etc for which you want to have the capability to change their values without releasing a new app version. Then you need to define those parameters with default values in an xml file as shown. You need to save the xml file in res/xml folder.

<?xml version="1.0" encoding="utf-8"?>
<defaultsMap>
    <entry>
        <key>toolbar_sub_title</key>
        <value>Sign Up</value>
    </entry>
    <entry>
        <key>toolbar_size</key>
        <value>50dp</value>
    </entry>
    <entry>
        <key>color_primary</key>
        <value>#64dd17</value>
    </entry>
    <entry>
        <key>hint_color</key>
        <value>#b71c1c</value>
    </entry>
    <entry>
        <key>status_bar_color</key>
        <value>#2fa50e</value>
    </entry>
    <entry>
        <key>register_button_text</key>
        <value>Sign Up</value>
    </entry>
    <entry>
        <key>color_accent</key>
        <value>#b71c1c</value>
    </entry>
</defaultsMap>

Applying Default Values

To use the defaults values defined in XML, you need to use Firebase remote config API. First get FirebaseRemoteConfig object by calling getInstance method on FirebaseRemoteConfig class.

Then create FirebaseRemoteConfigSettings object using FirebaseRemoteConfigSettings.Builder. You can use setDeveloperModeEnabled method on the builder to indicate that project is in development mode. Then add the FirebaseRemoteConfigSettings to FirebaseRemoteConfig by calling setConfigSettings method on FirebaseRemoteConfig.

Then, you need to add default configuration to FirebaseRemoteConfig object by calling setDefaults and passing the XML resource defined above.

To apply default values to UI controls, you need to get the default value for each property from FirebaseRemoteConfig object by calling various get methods on it.

Below code show instantiating FirebaseRemoteConfig object with default config.

 firebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder()
        .setDeveloperModeEnabled(true)
        .build();
firebaseRemoteConfig.setConfigSettings(configSettings);
firebaseRemoteConfig.setDefaults(R.xml.config_default); 

Below code shows reading values from FirebaseRemoteConfig object and setting them to UI objects.

 String toolbarSubTitle = firebaseRemoteConfig.getString("toolbar_sub_title");
toolbar.setSubtitle(toolbarSubTitle);

String colorPrimary = firebaseRemoteConfig.getString("color_primary");
toolbar.setBackgroundColor(Color.parseColor(colorPrimary)); 

Setting Parameter Values in Firebase Console

To assign different values for parameters defined in the in-app xml file which contains default values, you need to login to Firebase console, go to remote config, click add parameter and enter parameter name and new value. After adding parameters, you need to publish the changes to make them available for your app.

Firebase remote config adding parameters firebase console

Fetching Values from Remote Config Service

In your app, to fetch values from Firebase remote config service, you need to call fetch method on FirebaseRemoteConfig object passing cache expiration time in seconds. Until the cache time expires, call to fetch method reads values from local cache. After cache expires, it will fetch values from remote config service and resets cache expire time to the value passed to the fetch method.

Fetch method returns Task object to which you can add OnCompleteListener or OnSuccessListener to know the status of the fetch call. If it is successful, you can activate the fetched values from remote service by calling activateFetched method and then read parameter values using get methods on FirebaseRemoteConfig object to apply them to UI objects.

 firebaseRemoteConfig.fetch(cacheExpires)
.addOnCompleteListener(this, new OnCompleteListener<Void>() {
    @Override
    public void onComplete(Task<Void> task) {
        if (task.isSuccessful()) {
            Log.d("Remote", "fetched values from server");
            firebaseRemoteConfig.activateFetched();
        } else {
            Toast.makeText(RemoteConfigActivity.this,
                    "Failed to get remote configuration",
                    Toast.LENGTH_SHORT).show();
        }
        applyRemoteConfig();
    }
}); 

Firebase Remote Config Android Example Output

Example output with default values.

Firebase remote config android before remote config

Example output after applying remote values

Firebase remote config android example after remote config

Below is the example’s complete code.

Activity

import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.remoteconfig.FirebaseRemoteConfig;
import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings;

import zoftino.com.firestore.R;

public class RemoteConfigActivity extends AppCompatActivity {

    private FirebaseRemoteConfig firebaseRemoteConfig;
    private Toolbar toolbar;
    private EditText email;
    private EditText password;
    private Button regButton;


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

        bindFields();

        toolbar.setTitle("Firebase Remote Config");

        regButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(RemoteConfigActivity.this,
                        "Registration successful",
                        Toast.LENGTH_SHORT).show();
            }
        });

        //initialize Firebase remote config objects and apply default values
        initializeFirebaseRemoteConfig();
        //fetch remote values and apply
        fetchRemoteConfigValues();
    }

    private void bindFields(){
        regButton = findViewById(R.id.register_b);
        toolbar = findViewById(R.id.toolbar);
        email = findViewById(R.id.email);
        password = findViewById(R.id.password);
        regButton = findViewById(R.id.register_b);
    }
    private void initializeFirebaseRemoteConfig(){
        firebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
        FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder()
                .setDeveloperModeEnabled(true)
                .build();
        firebaseRemoteConfig.setConfigSettings(configSettings);
        firebaseRemoteConfig.setDefaults(R.xml.config_default);

        //apply default values
        applyRemoteConfig();
    }

    private void fetchRemoteConfigValues() {

        long cacheExpires = 5000;
        if (firebaseRemoteConfig.getInfo().getConfigSettings().isDeveloperModeEnabled()) {
            cacheExpires = 0;
        }

        firebaseRemoteConfig.fetch(cacheExpires)
                .addOnCompleteListener(this, new OnCompleteListener<Void>() {
                    @Override
                    public void onComplete(Task<Void> task) {
                        if (task.isSuccessful()) {
                            Log.d("Remote", "fetched values from server");
                            firebaseRemoteConfig.activateFetched();
                        } else {
                            Toast.makeText(RemoteConfigActivity.this,
                                    "Failed to get remote configuration",
                                    Toast.LENGTH_SHORT).show();
                        }
                        applyRemoteConfig();
                    }
                });
    }

    private void applyRemoteConfig(){
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            int statusBarColor = Color.parseColor(firebaseRemoteConfig.getString("status_bar_color"));
            this.getWindow().setStatusBarColor(statusBarColor);
        }

        String toolbarSubTitle = firebaseRemoteConfig.getString("toolbar_sub_title");
        toolbar.setSubtitle(toolbarSubTitle);


        String colorPrimary = firebaseRemoteConfig.getString("color_primary");
        toolbar.setBackgroundColor(Color.parseColor(colorPrimary));

        regButton.setText(firebaseRemoteConfig.getString("register_button_text"));

        int colorAccent = Color.parseColor(firebaseRemoteConfig.getString("color_accent"));
        regButton.setBackgroundColor(colorAccent);
    }

}

Activity Layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".OrderActivity">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <android.support.design.widget.TextInputLayout
        android:id="@+id/email_l"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/toolbar" >
        <android.support.design.widget.TextInputEditText
            android:id="@+id/email"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Enter Email"/>
    </android.support.design.widget.TextInputLayout>
    <android.support.design.widget.TextInputLayout
        android:id="@+id/password_l"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/email_l">
        <android.support.design.widget.TextInputEditText
            android:id="@+id/password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textPassword"
            android:hint="Enter Password"/>
    </android.support.design.widget.TextInputLayout>
    <Button
        android:id="@+id/register_b"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        style="@style/Widget.AppCompat.Button.Colored"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/password_l"
        android:text="Sign Up"/>
</android.support.constraint.ConstraintLayout>

A/B Testing

Releasing config changes to all users of your app is not a good idea as you don’t know how users react to the change. Pushing changes to a few users will help you understand the level of acceptance of your changes. Then depending on the outcome of the test, you can release the changes to more or rest of the users of your app. This type of experiment can be done using A/B test. You can define the A/B test in Firebase console.

To define A/B test, click A/B testing.

Firebase remote config a/b testing firebase console

Then click create experiment, fill the form and click next.

Firebase remote config creating a/b testing firebase console

Then add variants, define values for each property and variant that you want experiment.

Firebase remote config creating a/b testing variants

Then add metrics to measure the level of acceptance.

Firebase remote config creating a/b testing firebase console

Then click review, after reviewing the information, click start experiment button.

On the experiment screen which you can access by going to remote config, a/b testing and clicking the title of the experiment, you can monitor your goals and depending on the results, you can either publish the config change to more users or make modifications to it and do the experiment with new config.

If you want, you can even test the experiment when it is draft status on your test device.

Firebase remote config creating a/b testing draft

After clicking the experiment, in the next screen expand the details section, click manage device button.

Firebase remote config creating a/b testing mange test devices

Then you need to capture the test device Firebase token. You can get it from the logs when your app runs on the test device. You can print the toke in the logs by calling FirebaseInstanceId.getInstance().getToken(). Add it to the screen and save it. Then run the app and see the selected variant is pushed to the device.

Firebase remote config creating a/b testing test device token