Migra la tua app Android Parse su Firebase

Se sei un utente Parse alla ricerca di una soluzione Backend as a Service alternativa, Firebase potrebbe essere la scelta ideale per la tua app Android.

Questa guida descrive come integrare servizi specifici nella tua app. Per le istruzioni di base sulla configurazione di Firebase, consulta la guida alla configurazione di Android .

statistiche di Google

Google Analytics è una soluzione gratuita di misurazione delle app che fornisce informazioni dettagliate sull'utilizzo delle app e sul coinvolgimento degli utenti. Analytics si integra con tutte le funzionalità di Firebase e fornisce report illimitati per un massimo di 500 eventi distinti che puoi definire utilizzando l'SDK di Firebase.

Consulta i documenti di Google Analytics per saperne di più.

Strategia di migrazione suggerita

L'utilizzo di diversi fornitori di analisi è uno scenario comune che si applica facilmente a Google Analytics. Aggiungilo alla tua app per beneficiare degli eventi e delle proprietà utente che Analytics raccoglie automaticamente, come prima apertura, aggiornamento dell'app, modello del dispositivo, età.

Per gli eventi personalizzati e le proprietà utente, puoi utilizzare una strategia di doppia scrittura utilizzando sia Parse Analytics che Google Analytics per registrare eventi e proprietà, il che ti consente di implementare gradualmente la nuova soluzione.

Confronto di codici

Analizza Analytics

// Start collecting data
ParseAnalytics.trackAppOpenedInBackground(getIntent());

Map<String, String> dimensions = new HashMap<String, String>();
// Define ranges to bucket data points into meaningful segments
dimensions.put("priceRange", "1000-1500");
// Did the user filter the query?
dimensions.put("source", "craigslist");
// Do searches happen more often on weekdays or weekends?
dimensions.put("dayType", "weekday");

// Send the dimensions to Parse along with the 'search' event
ParseAnalytics.trackEvent("search", dimensions);

statistiche di Google

// Obtain the FirebaseAnalytics instance and start collecting data
mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);

Bundle params = new Bundle();
// Define ranges to bucket data points into meaningful segments
params.putString("priceRange", "1000-1500");
// Did the user filter the query?
params.putString("source", "craigslist");
// Do searches happen more often on weekdays or weekends?
params.putString("dayType", "weekday");

// Send the event
mFirebaseAnalytics.logEvent("search", params);

Database in tempo reale Firebase

Il Firebase Realtime Database è un database NoSQL ospitato sul cloud. I dati vengono archiviati come JSON e sincronizzati in tempo reale su ogni client connesso.

Consulta la documentazione del Firebase Realtime Database per saperne di più.

Differenze con l'analisi dei dati

Oggetti

In Parse memorizzi un ParseObject , o una sua sottoclasse, che contiene coppie chiave-valore di dati compatibili con JSON. I dati sono senza schema, il che significa che non è necessario specificare quali chiavi esistono su ciascun ParseObject .

Tutti i dati del Firebase Realtime Database vengono archiviati come oggetti JSON e non esiste un equivalente per ParseObject ; scrivi semplicemente nell'albero JSON i valori dei tipi che corrispondono ai tipi JSON disponibili. È possibile utilizzare oggetti Java per semplificare la lettura e la scrittura dal database.

Di seguito è riportato un esempio di come salvare i punteggi più alti di un gioco.

Analizzare
@ParseClassName("GameScore")
public class GameScore {
        public GameScore() {}
        public GameScore(Long score, String playerName, Boolean cheatMode) {
            setScore(score);
            setPlayerName(playerName);
            setCheatMode(cheatMode);
        }

        public void setScore(Long score) {
            set("score", score);
        }

        public Long getScore() {
            return getLong("score");
        }

        public void setPlayerName(String playerName) {
            set("playerName", playerName);
        }

        public String getPlayerName() {
            return getString("playerName");
        }

        public void setCheatMode(Boolean cheatMode) {
            return set("cheatMode", cheatMode);
        }

        public Boolean getCheatMode() {
            return getBoolean("cheatMode");
        }
}

// Must call Parse.registerSubclass(GameScore.class) in Application.onCreate
GameScore gameScore = new GameScore(1337, "Sean Plott", false);
gameScore.saveInBackground();
Base di fuoco
// Assuming we defined the GameScore class as:
public class GameScore {
        private Long score;
        private String playerName;
        private Boolean cheatMode;

        public GameScore() {}
        public GameScore(Long score, String playerName, Boolean cheatMode) {
            this.score = score;
            this.playerName = playerName;
            this.cheatMode = cheatMode;
        }

        public Long getScore() {
            return score;
        }

        public String getPlayerName() {
            return playerName;
        }

        public Boolean getCheatMode() {
            return cheatMode;
        }
}

// We would save it to our list of high scores as follows:
DatabaseReference mFirebaseRef = FirebaseDatabase.getInstance().getReference();
GameScore score = new GameScore(1337, "Sean Plott", false);
mFirebaseRef.child("scores").push().setValue(score);
Per maggiori dettagli consultare la guida Lettura e scrittura dati su Android .

Relazioni tra i dati

Un ParseObject può avere una relazione con un altro ParseObject : qualsiasi oggetto può utilizzare altri oggetti come valori.

Nel Firebase Realtime Database, le relazioni vengono espresse meglio utilizzando strutture di dati flat che dividono i dati in percorsi separati, in modo che possano essere scaricati in modo efficiente in chiamate separate.

Di seguito è riportato un esempio di come potresti strutturare la relazione tra i post in un'app di blogging e i relativi autori.

Analizzare
// Create the author
ParseObject myAuthor = new ParseObject("Author");
myAuthor.put("name", "Grace Hopper");
myAuthor.put("birthDate", "December 9, 1906");
myAuthor.put("nickname", "Amazing Grace");

// Create the post
ParseObject myPost = new ParseObject("Post");
myPost.put("title", "Announcing COBOL, a New Programming Language");

// Add a relation between the Post and the Author
myPost.put("parent", myAuthor);

// This will save both myAuthor and myPost
myPost.saveInBackground();
Base di fuoco
DatabaseReference firebaseRef = FirebaseDatabase.getInstance().getReference();
// Create the author
Map<String, String> myAuthor = new HashMap<String, String>();
myAuthor.put("name", "Grace Hopper");
myAuthor.put("birthDate", "December 9, 1906");
myAuthor.put("nickname", "Amazing Grace");

// Save the author
String myAuthorKey = "ghopper";
firebaseRef.child('authors').child(myAuthorKey).setValue(myAuthor);

// Create the post
Map<String, String> post = new HashMap<String, String>();
post.put("author", myAuthorKey);
post.put("title", "Announcing COBOL, a New Programming Language");
firebaseRef.child('posts').push().setValue(post);

Il risultato è il seguente layout dei dati.

{
  // Info about the authors
  "authors": {
    "ghopper": {
      "name": "Grace Hopper",
      "date_of_birth": "December 9, 1906",
      "nickname": "Amazing Grace"
    },
    ...
  },
  // Info about the posts: the "author" fields contains the key for the author
  "posts": {
    "-JRHTHaIs-jNPLXOQivY": {
      "author": "ghopper",
      "title": "Announcing COBOL, a New Programming Language"
    }
    ...
  }
}
Per maggiori dettagli, consulta la guida Struttura il tuo database .

Lettura dei dati

In Parse leggi i dati utilizzando l'ID di un oggetto Parse specifico o eseguendo query utilizzando ParseQuery .

In Firebase, recuperi i dati collegando un ascoltatore asincrono a un riferimento al database. Il listener viene attivato una volta per lo stato iniziale dei dati e di nuovo quando i dati cambiano, quindi non sarà necessario aggiungere alcun codice per determinare se i dati sono cambiati.

Quello che segue è un esempio di come recuperare i punteggi per un particolare giocatore, in base all'esempio presentato nella sezione "Oggetti" .

Analizzare
ParseQuery<ParseObject> query = ParseQuery.getQuery("GameScore");
query.whereEqualTo("playerName", "Dan Stemkoski");
query.findInBackground(new FindCallback<ParseObject>() {
    public void done(List<ParseObject> scoreList, ParseException e) {
        if (e == null) {
            for (ParseObject score: scoreList) {
                Log.d("score", "Retrieved: " + Long.toString(score.getLong("score")));
            }
        } else {
            Log.d("score", "Error: " + e.getMessage());
        }
    }
});
Base di fuoco
DatabaseReference mFirebaseRef = FirebaseDatabase.getInstance().getReference();
Query mQueryRef = mFirebaseRef.child("scores").orderByChild("playerName").equalTo("Dan Stemkoski");

// This type of listener is not one time, and you need to cancel it to stop
// receiving updates.
mQueryRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChild) {
        // This will fire for each matching child node.
        GameScore score = snapshot.getValue(GameScore.class);
        Log.d("score", "Retrieved: " + Long.toString(score.getScore());
    }
});
Per maggiori dettagli sui tipi disponibili di ascoltatori di eventi e su come ordinare e filtrare i dati, consultare la guida Lettura e scrittura dei dati su Android .

Strategia di migrazione suggerita

Ripensa i tuoi dati

Il Firebase Realtime Database è ottimizzato per sincronizzare i dati in millisecondi su tutti i client connessi e la struttura dei dati risultante è diversa dai dati principali di Parse. Ciò significa che il primo passo della migrazione è considerare quali modifiche richiedono i tuoi dati, tra cui:

  • Come gli oggetti Parse dovrebbero essere mappati ai dati Firebase
  • Se hai relazioni genitore-figlio, come dividere i tuoi dati su percorsi diversi in modo che possano essere scaricati in modo efficiente in chiamate separate.

Migra i tuoi dati

Dopo aver deciso come strutturare i dati in Firebase, devi pianificare come gestire il periodo durante il quale la tua app deve scrivere su entrambi i database. Le tue scelte sono:

Sincronizzazione in background

In questo scenario, hai due versioni dell'app: la vecchia versione che utilizza Parse e una nuova versione che utilizza Firebase. Le sincronizzazioni tra i due database sono gestite da Parse Cloud Code (Parse to Firebase), con il codice che ascolta le modifiche su Firebase e sincronizza tali modifiche con Parse. Prima di poter iniziare a utilizzare la nuova versione, è necessario:

  • Converti i tuoi Parse Data esistenti nella nuova struttura Firebase e scrivili nel Firebase Realtime Database.
  • Scrivi le funzioni Parse Cloud Code che utilizzano l'API REST Firebase per scrivere nel database Firebase Realtime le modifiche apportate in Parse Data dai vecchi client.
  • Scrivi e distribuisci codice che ascolta le modifiche su Firebase e le sincronizza con il database Parse.

Questo scenario garantisce una netta separazione tra il vecchio e il nuovo codice e mantiene semplici i client. Le sfide di questo scenario sono la gestione di set di dati di grandi dimensioni nell'esportazione iniziale e la garanzia che la sincronizzazione bidirezionale non generi ricorsione infinita.

Doppia scrittura

In questo scenario, scrivi una nuova versione dell'app che utilizza sia Firebase che Parse, utilizzando Parse Cloud Code per sincronizzare le modifiche apportate dai vecchi client da Parse Data al Firebase Realtime Database. Quando un numero sufficiente di persone esegue la migrazione dalla versione di sola analisi dell'app, puoi rimuovere il codice di analisi dalla versione a doppia scrittura.

Questo scenario non richiede alcun codice lato server. Gli svantaggi sono che i dati a cui non si accede non vengono migrati e che la dimensione dell'app aumenta con l'utilizzo di entrambi gli SDK.

Autenticazione Firebase

Firebase Authentication può autenticare gli utenti utilizzando password e provider di identità federati popolari come Google, Facebook e Twitter. Fornisce inoltre librerie dell'interfaccia utente per farti risparmiare il significativo investimento necessario per implementare e mantenere un'esperienza di autenticazione completa per la tua app su tutte le piattaforme.

Consulta la documentazione sull'autenticazione Firebase per saperne di più.

Differenze con l'autenticazione dell'analisi

Parse fornisce una classe utente specializzata denominata ParseUser che gestisce automaticamente le funzionalità richieste per la gestione dell'account utente. ParseUser è una sottoclasse di ParseObject , il che significa che i dati utente sono disponibili in Parse Data e possono essere estesi con campi aggiuntivi come qualsiasi altro ParseObject .

Un FirebaseUser ha un insieme fisso di proprietà di base, ovvero un ID univoco, un indirizzo email principale, un nome e un URL di foto, archiviate nel database utente di un progetto separato; tali proprietà possono essere aggiornate dall'utente. Non è possibile aggiungere direttamente altre proprietà all'oggetto FirebaseUser ; puoi invece archiviare le proprietà aggiuntive nel tuo Firebase Realtime Database.

Di seguito è riportato un esempio di come potresti registrare un utente e aggiungere un ulteriore campo per il numero di telefono.

Analizzare
ParseUser user = new ParseUser();
user.setUsername("my name");
user.setPassword("my pass");
user.setEmail("email@example.com");

// other fields can be set just like with ParseObject
user.put("phone", "650-253-0000");

user.signUpInBackground(new SignUpCallback() {
    public void done(ParseException e) {
        if (e == null) {
            // Hooray! Let them use the app now.
        } else {
            // Sign up didn't succeed. Look at the ParseException
            // to figure out what went wrong
        }
    }
});
Base di fuoco
FirebaseAuth mAuth = FirebaseAuth.getInstance();

mAuth.createUserWithEmailAndPassword("email@example.com", "my pass")
    .continueWithTask(new Continuation<AuthResult, Task<Void>> {
        @Override
        public Task<Void> then(Task<AuthResult> task) {
            if (task.isSuccessful()) {
                FirebaseUser user = task.getResult().getUser();
                DatabaseReference firebaseRef = FirebaseDatabase.getInstance().getReference();
                return firebaseRef.child("users").child(user.getUid()).child("phone").setValue("650-253-0000");
            } else {
                // User creation didn't succeed. Look at the task exception
                // to figure out what went wrong
                Log.w(TAG, "signInWithEmail", task.getException());
            }
        }
    });

Strategia di migrazione suggerita

Migrazione degli account

Per migrare gli account utente da Parse a Firebase, esporta il tuo database utente in un file JSON o CSV, quindi importa il file nel tuo progetto Firebase utilizzando il comando auth:import della CLI Firebase.

Innanzitutto, esporta il tuo database utente dalla console Parse o dal tuo database self-hosted. Ad esempio, un file JSON esportato dalla console Parse potrebbe essere simile al seguente:

{ // Username/password user
  "bcryptPassword": "$2a$10$OBp2hxB7TaYZgKyTiY48luawlTuYAU6BqzxJfpHoJMdZmjaF4HFh6",
  "email": "user@example.com",
  "username": "testuser",
  "objectId": "abcde1234",
  ...
},
{ // Facebook user
  "authData": {
    "facebook": {
      "access_token": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
      "expiration_date": "2017-01-02T03:04:05.006Z",
      "id": "1000000000"
    }
  },
  "username": "wXyZ987654321StUv",
  "objectId": "fghij5678",
  ...
}

Quindi, trasforma il file esportato nel formato richiesto dalla CLI Firebase. Utilizza l' objectId dei tuoi utenti Parse come localId dei tuoi utenti Firebase. Inoltre, base64 codifica i valori bcryptPassword da Parse e li utilizza nel campo passwordHash . Per esempio:

{
  "users": [
    {
      "localId": "abcde1234",  // Parse objectId
      "email": "user@example.com",
      "displayName": "testuser",
      "passwordHash": "JDJhJDEwJE9CcDJoeEI3VGFZWmdLeVRpWTQ4bHVhd2xUdVlBVTZCcXp4SmZwSG9KTWRabWphRjRIRmg2",
    },
    {
      "localId": "fghij5678",  // Parse objectId
      "displayName": "wXyZ987654321StUv",
      "providerUserInfo": [
        {
          "providerId": "facebook.com",
          "rawId": "1000000000",  // Facebook ID
        }
      ]
    }
  ]
}

Infine, importa il file trasformato con la CLI Firebase, specificando bcrypt come algoritmo hash:

firebase auth:import account_file.json --hash-algo=BCRYPT

Migrazione dei dati utente

Se stai archiviando dati aggiuntivi per i tuoi utenti, puoi migrarli a Firebase Realtime Database utilizzando le strategie descritte nella sezione sulla migrazione dei dati . Se esegui la migrazione degli account utilizzando il flusso descritto nella sezione di migrazione degli account , i tuoi account Firebase avranno gli stessi ID dei tuoi account Parse, consentendoti di migrare e riprodurre facilmente qualsiasi relazione digitata dall'ID utente.

Messaggistica cloud Firebase

Firebase Cloud Messaging (FCM) è una soluzione di messaggistica multipiattaforma che ti consente di inviare messaggi e notifiche in modo affidabile e senza alcun costo. Il compositore di notifiche è un servizio gratuito basato su Firebase Cloud Messaging che consente notifiche utente mirate per gli sviluppatori di app mobili.

Per ulteriori informazioni, consulta la documentazione di Firebase Cloud Messaging .

Differenze con le notifiche push di analisi

A ogni applicazione Parse installata su un dispositivo registrato per le notifiche è associato un oggetto Installation , in cui vengono archiviati tutti i dati necessari per indirizzare le notifiche. Installation è una sottoclasse di ParseUser , il che significa che puoi aggiungere tutti i dati aggiuntivi che desideri alle istanze Installation .

Il compositore di notifiche fornisce segmenti di utenti predefiniti in base a informazioni come app, versione dell'app e lingua del dispositivo. Puoi creare segmenti di utenti più complessi utilizzando gli eventi e le proprietà di Google Analytics per creare segmenti di pubblico. Consulta la guida di aiuto per il pubblico per saperne di più. Queste informazioni sul targeting non sono visibili nel Firebase Realtime Database.

Strategia di migrazione suggerita

Migrazione dei token del dispositivo

Al momento in cui scrivo, Parse Android SDK utilizza una versione precedente dei token di registrazione FCM, non compatibile con le funzionalità offerte dal compositore delle notifiche.

Puoi ottenere un nuovo token aggiungendo l'SDK FCM alla tua app; ciò tuttavia potrebbe invalidare il token utilizzato da Parse SDK per ricevere le notifiche. Se vuoi evitarlo, puoi configurare Parse SDK per utilizzare sia l'ID mittente di Parse che il tuo ID mittente. In questo modo non invalidi il token utilizzato da Parse SDK, ma tieni presente che questa soluzione alternativa smetterà di funzionare quando Parse chiuderà il suo progetto.

Migrazione dei canali agli argomenti FCM

Se utilizzi i canali di analisi per inviare notifiche, puoi migrare agli argomenti FCM, che forniscono lo stesso modello editore-abbonato. Per gestire la transizione da Parse a FCM, puoi scrivere una nuova versione dell'app che utilizza Parse SDK per annullare l'iscrizione ai canali Parse e FCM SDK per iscriversi agli argomenti FCM corrispondenti. In questa versione dell'app dovresti disabilitare la ricezione delle notifiche sull'SDK Parse, rimuovendo quanto segue dal manifest della tua app:

<service android:name="com.parse.PushService" />
<receiver android:name="com.parse.ParsePushBroadcastReceiver"
  android:exported="false">
<intent-filter>
<action android:name="com.parse.push.intent.RECEIVE" />
<action android:name="com.parse.push.intent.DELETE" />
<action android:name="com.parse.push.intent.OPEN" />
</intent-filter>
</receiver>
<receiver android:name="com.parse.GcmBroadcastReceiver"
  android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />

<!--
IMPORTANT: Change "com.parse.starter" to match your app's package name.
-->
<category android:name="com.parse.starter" />
</intent-filter>
</receiver>

<!--
IMPORTANT: Change "YOUR_SENDER_ID" to your GCM Sender Id.
-->
<meta-data android:name="com.parse.push.gcm_sender_id"
  android:value="id:YOUR_SENDER_ID" />;

Ad esempio, se il tuo utente è iscritto all'argomento "Giganti", dovresti fare qualcosa del tipo:

ParsePush.unsubscribeInBackground("Giants", new SaveCallback() {
    @Override
    public void done(ParseException e) {
        if (e == null) {
            FirebaseMessaging.getInstance().subscribeToTopic("Giants");
        } else {
            // Something went wrong unsubscribing
        }
    }
});

Usando questa strategia, puoi inviare messaggi sia al canale Parse che al corrispondente argomento FCM, supportando gli utenti sia della vecchia che della nuova versione. Quando un numero sufficiente di utenti ha effettuato la migrazione dalla versione di sola analisi dell'app, puoi disattivare tale versione e iniziare a inviare utilizzando solo FCM.

Consulta la documentazione sugli argomenti FCM per saperne di più.

Configurazione remota Firebase

Firebase Remote Config è un servizio cloud che ti consente di modificare il comportamento e l'aspetto della tua app senza richiedere agli utenti di scaricare un aggiornamento dell'app. Quando utilizzi Remote Config, crei valori predefiniti in-app che controllano il comportamento e l'aspetto della tua app. Successivamente, potrai utilizzare la console Firebase per sovrascrivere i valori predefiniti in-app per tutti gli utenti dell'app o per segmenti della tua base utenti.

Firebase Remote Config può essere molto utile durante le migrazioni nei casi in cui desideri testare soluzioni diverse ed essere in grado di spostare dinamicamente più client a un provider diverso. Ad esempio, se disponi di una versione della tua app che utilizza sia Firebase che Parse per i dati, potresti utilizzare una regola percentile casuale per determinare quali client leggono da Firebase e aumentare gradualmente la percentuale.

Per ulteriori informazioni su Firebase Remote Config, consulta l' introduzione a Remote Config .

Differenze con la configurazione di analisi

Con Parse config puoi aggiungere coppie chiave/valore alla tua app nella dashboard di Parse Config e quindi recuperare ParseConfig sul client. Ogni istanza ParseConfig che ottieni è sempre immutabile. Quando in futuro recupererai un nuovo ParseConfig dalla rete, non modificherà alcuna istanza ParseConfig esistente, ma ne creerà invece una nuova e la renderà disponibile tramite getCurrentConfig() .

Con Firebase Remote Config crei impostazioni predefinite in-app per coppie chiave/valore che puoi sovrascrivere dalla console Firebase e puoi utilizzare regole e condizioni per fornire variazioni sull'esperienza utente della tua app a diversi segmenti della tua base utenti. Firebase Remote Config implementa una classe singleton che rende disponibili le coppie chiave/valore per la tua app. Inizialmente il singleton restituisce i valori predefiniti definiti nell'app. Puoi recuperare un nuovo set di valori dal server in qualsiasi momento conveniente per la tua app; dopo che il nuovo set è stato recuperato con successo, puoi scegliere quando attivarlo per rendere disponibili i nuovi valori all'app.

Strategia di migrazione suggerita

Puoi passare a Firebase Remote Config copiando le coppie chiave/valore della configurazione Parse nella console Firebase e quindi distribuendo una nuova versione dell'app che utilizza Firebase Remote Config.

Se desideri sperimentare sia Parse Config che Firebase Remote Config, puoi distribuire una nuova versione dell'app che utilizza entrambi gli SDK finché un numero sufficiente di utenti non avrà eseguito la migrazione dalla versione solo Parse.

Confronto di codici

Analizzare

ParseConfig.getInBackground(new ConfigCallback() {
    @Override
    public void done(ParseConfig config, ParseException e) {
        if (e == null) {
            Log.d("TAG", "Yay! Config was fetched from the server.");
        } else {
            Log.e("TAG", "Failed to fetch. Using Cached Config.");
            config = ParseConfig.getCurrentConfig();
        }

        // Get the message from config or fallback to default value
        String welcomeMessage = config.getString("welcomeMessage", "Welcome!");
    }
});

Base di fuoco

mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
// Set defaults from an XML resource file stored in res/xml
mFirebaseRemoteConfig.setDefaults(R.xml.remote_config_defaults);

mFirebaseRemoteConfig.fetch()
    .addOnSuccessListener(new OnSuccessListener<Void>() {
        @Override
        public void onSuccess(Void aVoid) {
            Log.d("TAG", "Yay! Config was fetched from the server.");
            // Once the config is successfully fetched it must be activated before newly fetched
            // values are returned.
            mFirebaseRemoteConfig.activateFetched();
        }
    })
    .addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception exception) {
            Log.e("TAG", "Failed to fetch. Using last fetched or default.");
        }
    })

// ...

// When this is called, the value of the latest fetched and activated config is returned;
// if there's none, the default value is returned.
String welcomeMessage = mFirebaseRemoteConfig.getString("welcomeMessage");