Skip to content

Commit 432e501

Browse files
committed
Added JSON support for Firebase Remote Configs defaults
1 parent e54e328 commit 432e501

File tree

6 files changed

+152
-5
lines changed

6 files changed

+152
-5
lines changed

firebase-config/src/androidTest/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigIntegrationTest.java

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ public void setDefaultsAsync_goodXml_setsDefaults() throws Exception {
122122
ConfigContainer goodDefaultsXmlContainer = newDefaultsContainer(DEFAULTS_MAP);
123123
cachePutReturnsConfig(mockDefaultsCache, goodDefaultsXmlContainer);
124124

125-
Task<Void> task = frc.setDefaultsAsync(getResourceId("frc_good_defaults"));
125+
Task<Void> task = frc.setDefaultsAsync(getResourceId("frc_good_defaults", "xml"));
126126
Tasks.await(task);
127127

128128
// Assert defaults were set correctly.
@@ -136,7 +136,7 @@ public void setDefaultsAsync_emptyXml_setsEmptyDefaults() throws Exception {
136136
ConfigContainer emptyDefaultsXmlContainer = newDefaultsContainer(ImmutableMap.of());
137137
cachePutReturnsConfig(mockDefaultsCache, emptyDefaultsXmlContainer);
138138

139-
Task<Void> task = frc.setDefaultsAsync(getResourceId("frc_empty_defaults"));
139+
Task<Void> task = frc.setDefaultsAsync(getResourceId("frc_empty_defaults", "xml"));
140140
Tasks.await(task);
141141

142142
ArgumentCaptor<ConfigContainer> captor = ArgumentCaptor.forClass(ConfigContainer.class);
@@ -150,14 +150,53 @@ public void setDefaultsAsync_badXml_ignoresBadEntries() throws Exception {
150150
newDefaultsContainer(ImmutableMap.of("second_default_key", "second_default_value"));
151151
cachePutReturnsConfig(mockDefaultsCache, badDefaultsXmlContainer);
152152

153-
Task<Void> task = frc.setDefaultsAsync(getResourceId("frc_bad_defaults"));
153+
Task<Void> task = frc.setDefaultsAsync(getResourceId("frc_bad_defaults", "xml"));
154154
Tasks.await(task);
155155

156156
ArgumentCaptor<ConfigContainer> captor = ArgumentCaptor.forClass(ConfigContainer.class);
157157
verify(mockDefaultsCache).put(captor.capture());
158158
assertThat(captor.getValue()).isEqualTo(badDefaultsXmlContainer);
159159
}
160160

161+
@Test
162+
public void setDefaultsAsyncJson_goodJson_setsDefaults() throws Exception {
163+
ConfigContainer goodDefaultsJsonContainer = newDefaultsContainer(DEFAULTS_MAP);
164+
cachePutReturnsConfig(mockDefaultsCache, goodDefaultsJsonContainer);
165+
166+
Task<Void> task = frc.setDefaultsAsyncJson(getResourceId("frc_good_defaults_json", "raw"));
167+
Tasks.await(task);
168+
169+
ArgumentCaptor<ConfigContainer> captor = ArgumentCaptor.forClass(ConfigContainer.class);
170+
verify(mockDefaultsCache).put(captor.capture());
171+
assertThat(captor.getValue()).isEqualTo(goodDefaultsJsonContainer);
172+
}
173+
174+
@Test
175+
public void setDefaultsAsyncJson_emptyJson_setsEmptyDefaults() throws Exception {
176+
ConfigContainer emptyDefaultsJsonContainer = newDefaultsContainer(ImmutableMap.of());
177+
cachePutReturnsConfig(mockDefaultsCache, emptyDefaultsJsonContainer);
178+
179+
Task<Void> task = frc.setDefaultsAsyncJson(getResourceId("frc_empty_defaults_json", "raw"));
180+
Tasks.await(task);
181+
182+
ArgumentCaptor<ConfigContainer> captor = ArgumentCaptor.forClass(ConfigContainer.class);
183+
verify(mockDefaultsCache).put(captor.capture());
184+
assertThat(captor.getValue()).isEqualTo(emptyDefaultsJsonContainer);
185+
}
186+
187+
@Test
188+
public void setDefaultsAsyncJson_invalidJson_setsEmptyDefaults() throws Exception {
189+
ConfigContainer emptyDefaultsJsonContainer = newDefaultsContainer(ImmutableMap.of());
190+
cachePutReturnsConfig(mockDefaultsCache, emptyDefaultsJsonContainer);
191+
192+
Task<Void> task = frc.setDefaultsAsyncJson(getResourceId("frc_invalid_json_format", "raw"));
193+
Tasks.await(task);
194+
195+
ArgumentCaptor<ConfigContainer> captor = ArgumentCaptor.forClass(ConfigContainer.class);
196+
verify(mockDefaultsCache).put(captor.capture());
197+
assertThat(captor.getValue()).isEqualTo(emptyDefaultsJsonContainer);
198+
}
199+
161200
private static void cachePutReturnsConfig(
162201
ConfigCacheClient cacheClient, ConfigContainer container) {
163202
when(cacheClient.put(container)).thenReturn(Tasks.forResult(container));
@@ -171,9 +210,9 @@ private static ConfigContainer newDefaultsContainer(Map<String, String> configsM
171210
.build();
172211
}
173212

174-
private static int getResourceId(String xmlResourceName) {
213+
private static int getResourceId(String xmlResourceName, String defType) {
175214
Resources r = getInstrumentation().getTargetContext().getResources();
176215
return r.getIdentifier(
177-
xmlResourceName, "xml", getInstrumentation().getTargetContext().getPackageName());
216+
xmlResourceName, defType, getInstrumentation().getTargetContext().getPackageName());
178217
}
179218
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"first_default_key": "first_default_value",
3+
"second_default_key": "second_default_value",
4+
"third_default_key": "third_default_value"
5+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"key" : "value",
3+
"key Inside Json" : "keyInsideJson value",
4+
}

firebase-config/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import android.util.Log;
1919
import androidx.annotation.NonNull;
2020
import androidx.annotation.Nullable;
21+
import androidx.annotation.RawRes;
2122
import androidx.annotation.VisibleForTesting;
2223
import androidx.annotation.XmlRes;
2324
import com.google.android.gms.tasks.Task;
@@ -35,6 +36,7 @@
3536
import com.google.firebase.remoteconfig.internal.ConfigGetParameterHandler;
3637
import com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler;
3738
import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient;
39+
import com.google.firebase.remoteconfig.internal.DefaultsJsonParser;
3840
import com.google.firebase.remoteconfig.internal.DefaultsXmlParser;
3941
import com.google.firebase.remoteconfig.internal.rollouts.RolloutsStateSubscriptionsHandler;
4042
import java.util.ArrayList;
@@ -539,6 +541,18 @@ public Task<Void> setDefaultsAsync(@XmlRes int resourceId) {
539541
return setDefaultsWithStringsMapAsync(xmlDefaults);
540542
}
541543

544+
/**
545+
* Sets default configs using a JSON resource.
546+
*
547+
* @param resourceId Id for the JSON resource, which should be in your application's {@code
548+
* res/raw} folder.
549+
*/
550+
@NonNull
551+
public Task<Void> setDefaultsAsyncJson(@RawRes int resourceId) {
552+
Map<String, String> jsonDefaults = DefaultsJsonParser.getDefaultsFromJson(context, resourceId);
553+
return setDefaultsWithStringsMapAsync(jsonDefaults);
554+
}
555+
542556
/**
543557
* Deletes all activated, fetched and defaults configs and resets all Firebase Remote Config
544558
* settings.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.google.firebase.remoteconfig.internal;
2+
3+
import static com.google.firebase.remoteconfig.FirebaseRemoteConfig.TAG;
4+
5+
import android.content.Context;
6+
import android.content.res.Resources;
7+
import android.util.Log;
8+
import org.json.JSONException;
9+
import org.json.JSONObject;
10+
import java.io.IOException;
11+
import java.io.InputStream;
12+
import java.nio.charset.StandardCharsets;
13+
import java.util.HashMap;
14+
import java.util.Iterator;
15+
import java.util.Map;
16+
17+
/**
18+
* Parser for the defaults JSON file.
19+
*
20+
* <p>Firebase Remote Config (FRC) users can provide a JSON file with a map of default values to be
21+
* used when no fetched values are available. This class helps parse that JSON into a Java {@link
22+
* Map}.
23+
*
24+
* <p>The parser saves the key-value pairs directly from the JSON file and returns a map of all such pairs.
25+
*
26+
* <p>For example, consider the following JSON file:
27+
*
28+
* <pre>{@code
29+
* {
30+
* "first_default_key": "first_default_value",
31+
* "second_default_key": "second_default_value",
32+
* "third_default_key": "third_default_value"
33+
* }
34+
* }</pre>
35+
*
36+
* The parser would return a map with the following key-value pairs:
37+
* <ul>
38+
* <li>"first_default_key" -> "first_default_value"</li>
39+
* <li>"second_default_key" -> "second_default_value"</li>
40+
* <li>"third_default_key" -> "third_default_value"</li>
41+
* </ul>
42+
*
43+
* @author Laurentiu Rosu
44+
*/
45+
public class DefaultsJsonParser {
46+
/**
47+
* Returns a {@link Map} of default FRC values parsed from the defaults JSON file.
48+
*
49+
* @param context the application context.
50+
* @param resourceId the resource id of the defaults JSON file.
51+
*/
52+
public static Map<String, String> getDefaultsFromJson(Context context, int resourceId) {
53+
Map<String, String> defaultsMap = new HashMap<>();
54+
55+
try {
56+
Resources resources = context.getResources();
57+
if (resources == null) {
58+
Log.e(TAG, "Could not find the resources of the current context " +
59+
"while trying to set defaults from a JSON.");
60+
return defaultsMap;
61+
}
62+
63+
InputStream inputStream = resources.openRawResource(resourceId);
64+
int size = inputStream.available();
65+
byte[] buffer = new byte[size];
66+
inputStream.read(buffer);
67+
inputStream.close();
68+
String jsonString = new String(buffer, StandardCharsets.UTF_8);
69+
70+
JSONObject jsonObject = new JSONObject(jsonString);
71+
72+
Iterator<String> keys = jsonObject.keys();
73+
while (keys.hasNext()) {
74+
String key = keys.next();
75+
String value = jsonObject.getString(key);
76+
defaultsMap.put(key, value);
77+
}
78+
} catch (IOException | JSONException e) {
79+
Log.e(TAG, "Encountered an error while trying to parse the defaults JSON file.", e);
80+
return new HashMap<>();
81+
}
82+
return defaultsMap;
83+
}
84+
}

0 commit comments

Comments
 (0)