In this tutorial, we are going to look at how to secure your API credentials and make them easily updatable in production in Android with Java.

1. Setup android project

Let us start by setting up an Android app project.

In your root-level (project-level) Gradle file (build.gradle), add rules to include the Google Services Gradle plugin. Check that you have Google's Maven repository, as well.


buildscript {
    repositories {
        // Add the following line: Google's Maven repository
        google() 
    }
    dependencies {
        // Add the following line: Google Services plugin
        classpath 'com.google.gms:google-services:4.3.3'
    }
}

allprojects {
    repositories {
        // Add the following line: Google's Maven repository
        google()
    }
}

In your module (app-level) Gradle file (usually app/build.gradle), apply the Google Services Gradle plugin.


// Add the following line: Google Services plugin
apply plugin: 'com.google.gms.google-services'

To your module (app-level) Gradle file (usually app/build.gradle), add the dependencies that you want to use in your app.


// Lombok
compileOnly 'org.projectlombok:lombok:1.18.12'
annotationProcessor 'org.projectlombok:lombok:1.18.12'

// Retrofit 
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.2'

// Firebase remote config
implementation 'com.google.firebase:firebase-config:19.2.0'

Sync your app to ensure that all dependencies have the necessary versions.

2. Connect android application to Firebase

Before you can add Firebase to your Android app, you need to create a Firebase project to connect to your Android app.

  • Go to the Firebase console.
  • In the center of the project overview page, click the Android icon to launch the setup workflow. If you've already added an app to your Firebase project, click Add app to display the platform options.
  • Enter your app's package name in the Android package name field.
  • Enter other app information: App nickname and Debug signing certificate SHA-1.
  • Click Register app.

The next step is for you to add Firebase Android configuration file to your app.

  • Click Download google-services.json to obtain your Firebase Android config file (google-services.json).
  • Move your config file into the module (app-level) directory of your app.

This connects your app to Firebase.

3. Set Firebase Remote Config

We need to setup remote config parameters in the project created in Firebase console.

  • Select your project.
  • Click on Remote Config on the left navigation.
  • Click Add parameter then enter your Parameter key and Default value.
  • To add parameter description, click Add description then enter Description
  • Click Add parameter.
remote config

After adding all the needed parameters, click Publish changes to make then available in the applications.

4. Create Africa's Talking account and application

Before using Africa's Talking, you need to create an account, create your team and add your app.

  • Go to Africa's Talking
  • If you do not have an account, register and verify.
  • Click New Team
  • gEnter name then click Save
  • Select the newly created team and click Create app
  • Enter Name, Username, and select Country then click Save.

You need to generate an API key for the app we created.

  • Select the app created under Apps.
  • Click Settings->API key the on side navigation.
  • Enter your password and click Generate.
  • Copy and save the generated API key.

5. Setup Africa's Talking credentials to Remote Config

From the Africa's Talking app created above, you now have the username and API key. These are required for authentication when sending the SMS.

You need to add the Remote Config parameters for Africa's talking username, API key and Base URL.

In your Android app, setup Remote Config. Get a Remote Config object instance and set the minimum fetch interval to allow for frequent refreshes.


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
    setupFirebaseRemoteConfig();
}


private void setupFirebaseRemoteConfig() {
    FirebaseRemoteConfig config = FirebaseRemoteConfig.getInstance();
    FirebaseRemoteConfigSettings settings = new FirebaseRemoteConfigSettings
        .Builder()
        .setMinimumFetchIntervalInSeconds(3600)
        .build();
    config.setConfigSettingsAsync(settings);
}

You can set in-app default parameter values in the Remote Config object, so that your app behaves as intended before it connects to the Remote Config backend, and so that default values are available if none are set in the backend.

Define a set of parameter names and default parameter values using an XML resource file stored in your app's res/xml folder.


<?xml version="1.0" encoding="utf-8"?>
<defaultsMap>
    <entry>
        <key>africas_talking_base_url</key>
        <value>https://api.sandbox.africastalking.com/version1/</value>
    </entry>
</defaultsMap>

Add these values to the Remote Config.


config.setDefaultsAsync(R.xml.remote_config_defaults);

For each parameter, you can set a default value which will be eventually overridden by the corresponding Remote Config value.

Fetch parameter values from the Remote Config backend. Any values that you set in the backend are fetched and stored in the Remote Config object and made available to the app.


config.fetchAndActivate()
    .addOnCompleteListener(new OnCompleteListener<Boolean>() {
        @Override
        public void onComplete(@NonNull Task<Boolean> task) {
            if (!task.isSuccessful()) {
                // Handle on failed
            }
        }
    });

You need to fetch the current active values of the Remote Config parameters.


FirebaseRemoteConfig config = FirebaseRemoteConfig.getInstance();
String apiKey = config.getString("africas_talking_api_key");
String username = config.getString("africas_talking_username");
String baseUrl = config.getString("africas_talking_base_url");

6. Send SMS

In this case, we are going to use Retrofit to consume Africa's Talking SMS API.

You need to start by configuring Retrofit service.


private static Retrofit africasTalking;

private static OkHttpClient httpClient() {
    return new OkHttpClient.Builder()
        .followRedirects(true)
        .followSslRedirects(true)
        .retryOnConnectionFailure(true)
        .build();
}

public static Retrofit getAfricasTalking(@NonNull String baseUrl) {
    if (africasTalking == null) {
        africasTalking = new Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(httpClient())
            .addConverterFactory(GsonConverterFactory
                .create(new GsonBuilder().setLenient().create()))
            .build();
    }
    if (!africasTalking.baseUrl().toString().equals(baseUrl)) {
        africasTalking = africasTalking.newBuilder().baseUrl(baseUrl).build();
    }
    return africasTalking;
}

HTTP API interface.


public interface AfricasTalkingService {
    @Headers(value = {"Accept: application/json"})
    @POST(value = "messaging")
    @FormUrlEncoded
    Call<SendSmsResponse> sendSms(
        @Header("apiKey") String apiKey, 
        @Field("username") String username, 
        @Field("to") String recipient, 
        @Field("message") String message);
}

HTTP API response body object.


@ToString
@Setter
@Getter
public class SendSmsResponse implements Serializable {
    @SerializedName(value = "SMSMessageData")
    private SmsMessageData messageData;
}


@ToString
@Setter
@Getter
public class SmsMessageData implements Serializable {
    @SerializedName(value = "Message")
    private String message;

    @SerializedName(value = "Recipients")
    private List<SmsMessageRecipientData> recipients;
}


@ToString
@Setter
@Getter
public class SmsMessageRecipientData implements Serializable {
    @SerializedName(value = "statusCode")
    private int code;

    @SerializedName(value = "number")
    private String number;

    @SerializedName(value = "status")
    private String status;

    @SerializedName(value = "cost")
    private String cost;

    @SerializedName(value = "messageId")
    private String messageId;
}

Now your sendSms method.


public void sendSms(
    @NonNull String apiKey,
    @NonNull String username,
    @NonNull String baseUrl,
    @NonNull String recipient, 
    @NonNull String message
) {
    AfricasTalkingService at = RetrofitConfigurer
            .getAfricasTalking(baseUrl)
            .create(AfricasTalkingService.class);
            
    at.sendSms(apiKey, username, recipient, message)
        .enqueue(new Callback<SendSmsResponse>() {
            @Override
            public void onResponse(
                @NonNull Call<SendSmsResponse> call, 
                @NonNull Response<SendSmsResponse> response
            ) {
                if (response.isSuccessful()) {
                    // handle success
                } else {
                    // handle failed
                }
            }
            
            @Override
            public void onFailure(
                @NonNull Call<SendSmsResponse> call, 
                @NonNull Throwable t
            ) {
                // handle error
            }
        });
}

Calling sendSms method.


FirebaseRemoteConfig config = FirebaseRemoteConfig.getInstance();
String apiKey = config.getString("africas_talking_api_key");
String username = config.getString("africas_talking_username");
String baseUrl = config.getString("africas_talking_base_url");

sendSms(apiKey, username, baseUrl, "+2547XXXXXXXX", "This is test");

7. Conclusions

This tutorial covered how to store your API credentials using Firebase Remote Config. This can also be used to store other sensitive data in your app.

By using Remote Config gives you the advantage of changing parameter values in production without having another build.

The implementation of this Firebase Remote Config Tutorial can be found in the GitHub project.

You've successfully subscribed to Decoded For Devs
Welcome back! You've successfully signed in.
Great! You've successfully signed up.
Your link has expired
Success! Your account is fully activated, you now have access to all content.