diff --git a/firebase-config/src/androidTest/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigIntegrationTest.java b/firebase-config/src/androidTest/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigIntegrationTest.java index cc8c259836e..177d2ae2b14 100644 --- a/firebase-config/src/androidTest/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigIntegrationTest.java +++ b/firebase-config/src/androidTest/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigIntegrationTest.java @@ -122,7 +122,7 @@ public void setDefaultsAsync_goodXml_setsDefaults() throws Exception { ConfigContainer goodDefaultsXmlContainer = newDefaultsContainer(DEFAULTS_MAP); cachePutReturnsConfig(mockDefaultsCache, goodDefaultsXmlContainer); - Task task = frc.setDefaultsAsync(getResourceId("frc_good_defaults")); + Task task = frc.setDefaultsAsync(getResourceId("frc_good_defaults", "xml")); Tasks.await(task); // Assert defaults were set correctly. @@ -136,7 +136,7 @@ public void setDefaultsAsync_emptyXml_setsEmptyDefaults() throws Exception { ConfigContainer emptyDefaultsXmlContainer = newDefaultsContainer(ImmutableMap.of()); cachePutReturnsConfig(mockDefaultsCache, emptyDefaultsXmlContainer); - Task task = frc.setDefaultsAsync(getResourceId("frc_empty_defaults")); + Task task = frc.setDefaultsAsync(getResourceId("frc_empty_defaults", "xml")); Tasks.await(task); ArgumentCaptor captor = ArgumentCaptor.forClass(ConfigContainer.class); @@ -150,7 +150,7 @@ public void setDefaultsAsync_badXml_ignoresBadEntries() throws Exception { newDefaultsContainer(ImmutableMap.of("second_default_key", "second_default_value")); cachePutReturnsConfig(mockDefaultsCache, badDefaultsXmlContainer); - Task task = frc.setDefaultsAsync(getResourceId("frc_bad_defaults")); + Task task = frc.setDefaultsAsync(getResourceId("frc_bad_defaults", "xml")); Tasks.await(task); ArgumentCaptor captor = ArgumentCaptor.forClass(ConfigContainer.class); @@ -158,6 +158,45 @@ public void setDefaultsAsync_badXml_ignoresBadEntries() throws Exception { assertThat(captor.getValue()).isEqualTo(badDefaultsXmlContainer); } + @Test + public void setDefaultsAsyncJson_goodJson_setsDefaults() throws Exception { + ConfigContainer goodDefaultsJsonContainer = newDefaultsContainer(DEFAULTS_MAP); + cachePutReturnsConfig(mockDefaultsCache, goodDefaultsJsonContainer); + + Task task = frc.setDefaultsAsyncJson(getResourceId("frc_good_defaults_json", "raw")); + Tasks.await(task); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ConfigContainer.class); + verify(mockDefaultsCache).put(captor.capture()); + assertThat(captor.getValue()).isEqualTo(goodDefaultsJsonContainer); + } + + @Test + public void setDefaultsAsyncJson_emptyJson_setsEmptyDefaults() throws Exception { + ConfigContainer emptyDefaultsJsonContainer = newDefaultsContainer(ImmutableMap.of()); + cachePutReturnsConfig(mockDefaultsCache, emptyDefaultsJsonContainer); + + Task task = frc.setDefaultsAsyncJson(getResourceId("frc_empty_defaults_json", "raw")); + Tasks.await(task); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ConfigContainer.class); + verify(mockDefaultsCache).put(captor.capture()); + assertThat(captor.getValue()).isEqualTo(emptyDefaultsJsonContainer); + } + + @Test + public void setDefaultsAsyncJson_invalidJson_setsEmptyDefaults() throws Exception { + ConfigContainer emptyDefaultsJsonContainer = newDefaultsContainer(ImmutableMap.of()); + cachePutReturnsConfig(mockDefaultsCache, emptyDefaultsJsonContainer); + + Task task = frc.setDefaultsAsyncJson(getResourceId("frc_invalid_json_format", "raw")); + Tasks.await(task); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ConfigContainer.class); + verify(mockDefaultsCache).put(captor.capture()); + assertThat(captor.getValue()).isEqualTo(emptyDefaultsJsonContainer); + } + private static void cachePutReturnsConfig( ConfigCacheClient cacheClient, ConfigContainer container) { when(cacheClient.put(container)).thenReturn(Tasks.forResult(container)); @@ -171,9 +210,9 @@ private static ConfigContainer newDefaultsContainer(Map configsM .build(); } - private static int getResourceId(String xmlResourceName) { + private static int getResourceId(String xmlResourceName, String defType) { Resources r = getInstrumentation().getTargetContext().getResources(); return r.getIdentifier( - xmlResourceName, "xml", getInstrumentation().getTargetContext().getPackageName()); + xmlResourceName, defType, getInstrumentation().getTargetContext().getPackageName()); } } diff --git a/firebase-config/src/androidTest/res/raw/frc_empty_defaults_json.json b/firebase-config/src/androidTest/res/raw/frc_empty_defaults_json.json new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/firebase-config/src/androidTest/res/raw/frc_empty_defaults_json.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/firebase-config/src/androidTest/res/raw/frc_good_defaults_json.json b/firebase-config/src/androidTest/res/raw/frc_good_defaults_json.json new file mode 100644 index 00000000000..73f8500a2de --- /dev/null +++ b/firebase-config/src/androidTest/res/raw/frc_good_defaults_json.json @@ -0,0 +1,5 @@ +{ + "first_default_key": "first_default_value", + "second_default_key": "second_default_value", + "third_default_key": "third_default_value" +} \ No newline at end of file diff --git a/firebase-config/src/androidTest/res/raw/frc_invalid_json_format.json b/firebase-config/src/androidTest/res/raw/frc_invalid_json_format.json new file mode 100644 index 00000000000..4ae6ae36ff3 --- /dev/null +++ b/firebase-config/src/androidTest/res/raw/frc_invalid_json_format.json @@ -0,0 +1,4 @@ +{ + "key" : "value", + "key Inside Json" : "keyInsideJson value", +} \ No newline at end of file diff --git a/firebase-config/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java b/firebase-config/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java index abd09dd0330..7088d6931a7 100644 --- a/firebase-config/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java +++ b/firebase-config/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java @@ -18,6 +18,7 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RawRes; import androidx.annotation.VisibleForTesting; import androidx.annotation.XmlRes; import com.google.android.gms.tasks.Task; @@ -35,6 +36,7 @@ import com.google.firebase.remoteconfig.internal.ConfigGetParameterHandler; import com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler; import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient; +import com.google.firebase.remoteconfig.internal.DefaultsJsonParser; import com.google.firebase.remoteconfig.internal.DefaultsXmlParser; import com.google.firebase.remoteconfig.internal.rollouts.RolloutsStateSubscriptionsHandler; import java.util.ArrayList; @@ -539,6 +541,18 @@ public Task setDefaultsAsync(@XmlRes int resourceId) { return setDefaultsWithStringsMapAsync(xmlDefaults); } + /** + * Sets default configs using a JSON resource. + * + * @param resourceId Id for the JSON resource, which should be in your application's {@code + * res/raw} folder. + */ + @NonNull + public Task setDefaultsAsyncJson(@RawRes int resourceId) { + Map jsonDefaults = DefaultsJsonParser.getDefaultsFromJson(context, resourceId); + return setDefaultsWithStringsMapAsync(jsonDefaults); + } + /** * Deletes all activated, fetched and defaults configs and resets all Firebase Remote Config * settings. diff --git a/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/DefaultsJsonParser.java b/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/DefaultsJsonParser.java new file mode 100644 index 00000000000..c2d05651566 --- /dev/null +++ b/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/DefaultsJsonParser.java @@ -0,0 +1,84 @@ +package com.google.firebase.remoteconfig.internal; + +import static com.google.firebase.remoteconfig.FirebaseRemoteConfig.TAG; + +import android.content.Context; +import android.content.res.Resources; +import android.util.Log; +import org.json.JSONException; +import org.json.JSONObject; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * Parser for the defaults JSON file. + * + *

Firebase Remote Config (FRC) users can provide a JSON file with a map of default values to be + * used when no fetched values are available. This class helps parse that JSON into a Java {@link + * Map}. + * + *

The parser saves the key-value pairs directly from the JSON file and returns a map of all such pairs. + * + *

For example, consider the following JSON file: + * + *

{@code
+ * {
+ *   "first_default_key": "first_default_value",
+ *   "second_default_key": "second_default_value",
+ *   "third_default_key": "third_default_value"
+ * }
+ * }
+ * + * The parser would return a map with the following key-value pairs: + *
    + *
  • "first_default_key" -> "first_default_value"
  • + *
  • "second_default_key" -> "second_default_value"
  • + *
  • "third_default_key" -> "third_default_value"
  • + *
+ * + * @author Laurentiu Rosu + */ +public class DefaultsJsonParser { + /** + * Returns a {@link Map} of default FRC values parsed from the defaults JSON file. + * + * @param context the application context. + * @param resourceId the resource id of the defaults JSON file. + */ + public static Map getDefaultsFromJson(Context context, int resourceId) { + Map defaultsMap = new HashMap<>(); + + try { + Resources resources = context.getResources(); + if (resources == null) { + Log.e(TAG, "Could not find the resources of the current context " + + "while trying to set defaults from a JSON."); + return defaultsMap; + } + + InputStream inputStream = resources.openRawResource(resourceId); + int size = inputStream.available(); + byte[] buffer = new byte[size]; + inputStream.read(buffer); + inputStream.close(); + String jsonString = new String(buffer, StandardCharsets.UTF_8); + + JSONObject jsonObject = new JSONObject(jsonString); + + Iterator keys = jsonObject.keys(); + while (keys.hasNext()) { + String key = keys.next(); + String value = jsonObject.getString(key); + defaultsMap.put(key, value); + } + } catch (IOException | JSONException e) { + Log.e(TAG, "Encountered an error while trying to parse the defaults JSON file.", e); + return new HashMap<>(); + } + return defaultsMap; + } +}