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.
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.
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>
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));
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.
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();
}
});
Example output with default values.
Example output after applying remote values
Below is the example’s complete code.
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);
}
}
<?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>
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.
Then click create experiment, fill the form and click next.
Then add variants, define values for each property and variant that you want experiment.
Then add metrics to measure the level of acceptance.
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.
After clicking the experiment, in the next screen expand the details section, click manage device button.
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.