Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom signals support in Remote Config. #6539

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions firebase-config/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ package com.google.firebase.remoteconfig {
method public void remove();
}

public class CustomSignals {
}

public static class CustomSignals.Builder {
ctor public CustomSignals.Builder();
method @NonNull public com.google.firebase.remoteconfig.CustomSignals build();
method @NonNull public com.google.firebase.remoteconfig.CustomSignals.Builder put(@NonNull String, @Nullable String);
method @NonNull public com.google.firebase.remoteconfig.CustomSignals.Builder put(@NonNull String, long);
method @NonNull public com.google.firebase.remoteconfig.CustomSignals.Builder put(@NonNull String, double);
}

public class FirebaseRemoteConfig {
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Boolean> activate();
method @NonNull public com.google.firebase.remoteconfig.ConfigUpdateListenerRegistration addOnConfigUpdateListener(@NonNull com.google.firebase.remoteconfig.ConfigUpdateListener);
Expand All @@ -35,6 +46,7 @@ package com.google.firebase.remoteconfig {
method @NonNull public com.google.firebase.remoteconfig.FirebaseRemoteConfigValue getValue(@NonNull String);
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Void> reset();
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Void> setConfigSettingsAsync(@NonNull com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings);
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Void> setCustomSignals(@NonNull com.google.firebase.remoteconfig.CustomSignals);
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Void> setDefaultsAsync(@NonNull java.util.Map<java.lang.String,java.lang.Object>);
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Void> setDefaultsAsync(@XmlRes int);
field public static final boolean DEFAULT_VALUE_FOR_BOOLEAN = false;
Expand Down Expand Up @@ -121,6 +133,7 @@ package com.google.firebase.remoteconfig {
}

public final class RemoteConfigKt {
method @NonNull public static com.google.firebase.remoteconfig.CustomSignals customSignals(@NonNull kotlin.jvm.functions.Function1<? super com.google.firebase.remoteconfig.CustomSignals.Builder,kotlin.Unit> builder);
method @NonNull public static operator com.google.firebase.remoteconfig.FirebaseRemoteConfigValue get(@NonNull com.google.firebase.remoteconfig.FirebaseRemoteConfig, @NonNull String key);
method @NonNull public static kotlinx.coroutines.flow.Flow<com.google.firebase.remoteconfig.ConfigUpdate> getConfigUpdates(@NonNull com.google.firebase.remoteconfig.FirebaseRemoteConfig);
method @NonNull public static com.google.firebase.remoteconfig.FirebaseRemoteConfig getRemoteConfig(@NonNull com.google.firebase.Firebase);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import com.google.firebase.installations.FirebaseInstallationsApi
import com.google.firebase.remoteconfig.internal.ConfigCacheClient
import com.google.firebase.remoteconfig.internal.ConfigFetchHandler
import com.google.firebase.remoteconfig.internal.ConfigGetParameterHandler
import com.google.firebase.remoteconfig.internal.ConfigMetadataClient
import com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler
import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient
import com.google.firebase.remoteconfig.internal.rollouts.RolloutsStateSubscriptionsHandler
import java.util.concurrent.Executor

Expand All @@ -41,7 +41,7 @@ fun createRemoteConfig(
defaultConfigsCache: ConfigCacheClient,
fetchHandler: ConfigFetchHandler,
getHandler: ConfigGetParameterHandler,
frcMetadata: ConfigMetadataClient,
frcSharedPrefs: ConfigSharedPrefsClient,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we are already making changes here, it sounds like a good idea to revisit naming, is frc adding any valuable meaning as part of the name? If not, I'd rather rename it to something more descriptive.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the suggestion! The prefix frc here specifically refers for storing Remote Config related key-value pairs in shared preferences. It helps differentiate these preferences from other shared preferences. Do you have any other suggestions?

realtimeHandler: ConfigRealtimeHandler,
rolloutsStateSubscriptionsHandler: RolloutsStateSubscriptionsHandler
): FirebaseRemoteConfig {
Expand All @@ -56,7 +56,7 @@ fun createRemoteConfig(
defaultConfigsCache,
fetchHandler,
getHandler,
frcMetadata,
frcSharedPrefs,
realtimeHandler,
rolloutsStateSubscriptionsHandler
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import com.google.firebase.remoteconfig.createRemoteConfig
import com.google.firebase.remoteconfig.internal.ConfigCacheClient
import com.google.firebase.remoteconfig.internal.ConfigFetchHandler
import com.google.firebase.remoteconfig.internal.ConfigGetParameterHandler
import com.google.firebase.remoteconfig.internal.ConfigMetadataClient
import com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler
import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient
import com.google.firebase.remoteconfig.internal.rollouts.RolloutsStateSubscriptionsHandler
import org.junit.After
import org.junit.Before
Expand Down Expand Up @@ -142,7 +142,7 @@ class ConfigTests : BaseTestCase() {
defaultConfigsCache = mock(ConfigCacheClient::class.java),
fetchHandler = mock(ConfigFetchHandler::class.java),
getHandler = mockGetHandler,
frcMetadata = mock(ConfigMetadataClient::class.java),
frcSharedPrefs = mock(ConfigSharedPrefsClient::class.java),
realtimeHandler = mock(ConfigRealtimeHandler::class.java),
rolloutsStateSubscriptionsHandler = mock(RolloutsStateSubscriptionsHandler::class.java)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
import com.google.firebase.remoteconfig.internal.ConfigContainer;
import com.google.firebase.remoteconfig.internal.ConfigFetchHandler;
import com.google.firebase.remoteconfig.internal.ConfigGetParameterHandler;
import com.google.firebase.remoteconfig.internal.ConfigMetadataClient;
import com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler;
import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient;
import com.google.firebase.remoteconfig.internal.rollouts.RolloutsStateSubscriptionsHandler;
import java.util.Date;
import java.util.HashMap;
Expand All @@ -60,7 +60,7 @@ public class FirebaseRemoteConfigIntegrationTest {
@Mock private ConfigCacheClient mockDefaultsCache;
@Mock private ConfigFetchHandler mockFetchHandler;
@Mock private ConfigGetParameterHandler mockGetHandler;
@Mock private ConfigMetadataClient metadataClient;
@Mock private ConfigSharedPrefsClient sharedPrefsClient;
@Mock private ConfigRealtimeHandler mockConfigRealtimeHandler;
@Mock private RolloutsStateSubscriptionsHandler mockRolloutsStateSubscriptionHandler;

Expand Down Expand Up @@ -112,7 +112,7 @@ public void setUp() {
mockDefaultsCache,
mockFetchHandler,
mockGetHandler,
metadataClient,
sharedPrefsClient,
mockConfigRealtimeHandler,
mockRolloutsStateSubscriptionHandler);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.firebase.remoteconfig;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;

/**
* A container type to represent key/value pairs of heterogeneous types to be set as custom signals
* in {@link FirebaseRemoteConfig#setCustomSignals}.
*/
public class CustomSignals {
rlazo marked this conversation as resolved.
Show resolved Hide resolved
final Map<String, String> customSignals;

/** Builder for constructing {@link CustomSignals} instances. */
public static class Builder {
tusharkhandelwal8 marked this conversation as resolved.
Show resolved Hide resolved
private Map<String, String> customSignals = new HashMap<String, String>();

/**
* Adds a custom signal with a value that can be a string or null to the builder.
*
* @param key The key for the custom signal.
* @param value The string value associated with the key. Can be null.
* @return This Builder instance to allow chaining of method calls.
*/
@NonNull
public Builder put(@NonNull String key, @Nullable String value) {
customSignals.put(key, value);
return this;
}

/**
* Adds a custom signal with a long value to the builder.
*
* @param key The key for the custom signal.
* @param value The long value for the custom signal.
* @return This Builder instance to allow chaining of method calls.
*/
@NonNull
public Builder put(@NonNull String key, long value) {
customSignals.put(key, Long.toString(value));
return this;
}

/**
* Adds a custom signal with a double value to the builder.
*
* @param key The key for the custom signal.
* @param value The double value for the custom signal.
* @return This Builder instance to allow chaining of method calls.
*/
@NonNull
public Builder put(@NonNull String key, double value) {
customSignals.put(key, Double.toString(value));
return this;
}

/**
* Creates a {@link CustomSignals} instance with the added custom signals.
*
* @return The constructed {@link CustomSignals} instance.
*/
@NonNull
public CustomSignals build() {
return new CustomSignals(this);
}
}

CustomSignals(@NonNull Builder builder) {
this.customSignals = builder.customSignals;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
import com.google.firebase.remoteconfig.internal.ConfigFetchHandler;
import com.google.firebase.remoteconfig.internal.ConfigFetchHandler.FetchResponse;
import com.google.firebase.remoteconfig.internal.ConfigGetParameterHandler;
import com.google.firebase.remoteconfig.internal.ConfigMetadataClient;
import com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler;
import com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient;
import com.google.firebase.remoteconfig.internal.DefaultsXmlParser;
import com.google.firebase.remoteconfig.internal.rollouts.RolloutsStateSubscriptionsHandler;
import java.util.ArrayList;
Expand Down Expand Up @@ -160,7 +160,7 @@ public static FirebaseRemoteConfig getInstance(@NonNull FirebaseApp app) {
private final ConfigCacheClient defaultConfigsCache;
private final ConfigFetchHandler fetchHandler;
private final ConfigGetParameterHandler getHandler;
private final ConfigMetadataClient frcMetadata;
private final ConfigSharedPrefsClient frcSharedPrefs;
private final FirebaseInstallationsApi firebaseInstallations;
private final ConfigRealtimeHandler configRealtimeHandler;
private final RolloutsStateSubscriptionsHandler rolloutsStateSubscriptionsHandler;
Expand All @@ -181,7 +181,7 @@ public static FirebaseRemoteConfig getInstance(@NonNull FirebaseApp app) {
ConfigCacheClient defaultConfigsCache,
ConfigFetchHandler fetchHandler,
ConfigGetParameterHandler getHandler,
ConfigMetadataClient frcMetadata,
ConfigSharedPrefsClient frcSharedPrefs,
ConfigRealtimeHandler configRealtimeHandler,
RolloutsStateSubscriptionsHandler rolloutsStateSubscriptionsHandler) {
this.context = context;
Expand All @@ -194,7 +194,7 @@ public static FirebaseRemoteConfig getInstance(@NonNull FirebaseApp app) {
this.defaultConfigsCache = defaultConfigsCache;
this.fetchHandler = fetchHandler;
this.getHandler = getHandler;
this.frcMetadata = frcMetadata;
this.frcSharedPrefs = frcSharedPrefs;
this.configRealtimeHandler = configRealtimeHandler;
this.rolloutsStateSubscriptionsHandler = rolloutsStateSubscriptionsHandler;
}
Expand All @@ -208,18 +208,18 @@ public Task<FirebaseRemoteConfigInfo> ensureInitialized() {
Task<ConfigContainer> activatedConfigsTask = activatedConfigsCache.get();
Task<ConfigContainer> defaultsConfigsTask = defaultConfigsCache.get();
Task<ConfigContainer> fetchedConfigsTask = fetchedConfigsCache.get();
Task<FirebaseRemoteConfigInfo> metadataTask = Tasks.call(executor, this::getInfo);
Task<FirebaseRemoteConfigInfo> sharedPrefsTask = Tasks.call(executor, this::getInfo);
Task<String> installationIdTask = firebaseInstallations.getId();
Task<InstallationTokenResult> installationTokenTask = firebaseInstallations.getToken(false);

return Tasks.whenAllComplete(
activatedConfigsTask,
defaultsConfigsTask,
fetchedConfigsTask,
metadataTask,
sharedPrefsTask,
installationIdTask,
installationTokenTask)
.continueWith(executor, (unusedListOfCompletedTasks) -> metadataTask.getResult());
.continueWith(executor, (unusedListOfCompletedTasks) -> sharedPrefsTask.getResult());
}

/**
Expand Down Expand Up @@ -475,7 +475,7 @@ public Map<String, FirebaseRemoteConfigValue> getAll() {
*/
@NonNull
public FirebaseRemoteConfigInfo getInfo() {
return frcMetadata.getInfo();
return frcSharedPrefs.getInfo();
}

/**
Expand All @@ -488,7 +488,7 @@ public Task<Void> setConfigSettingsAsync(@NonNull FirebaseRemoteConfigSettings s
return Tasks.call(
executor,
() -> {
frcMetadata.setConfigSettings(settings);
frcSharedPrefs.setConfigSettings(settings);

// Return value required; return null for Void.
return null;
Expand Down Expand Up @@ -548,14 +548,14 @@ public Task<Void> setDefaultsAsync(@XmlRes int resourceId) {
@NonNull
public Task<Void> reset() {
// Use a Task to avoid throwing potential file I/O errors to the caller and because
// frcMetadata's clear call is blocking.
// frcSharedPrefs's clear call is blocking.
return Tasks.call(
executor,
() -> {
activatedConfigsCache.clear();
fetchedConfigsCache.clear();
defaultConfigsCache.clear();
frcMetadata.clear();
frcSharedPrefs.clear();
return null;
});
}
Expand Down Expand Up @@ -652,6 +652,27 @@ private Task<Void> setDefaultsWithStringsMapAsync(Map<String, String> defaultsSt
FirebaseExecutors.directExecutor(), (unusedContainer) -> Tasks.forResult(null));
}

/**
* Asynchronously changes the custom signals for this {@link FirebaseRemoteConfig} instance.
*
* <p>The {@code customSignals} parameter should be an instance of {@link CustomSignals}.
*
* <p>Custom signals are subject to limits on the size of key/value pairs and the total
* number of signals. Any calls that exceed these limits will be discarded.
*
* @param customSignals A dictionary of keys and the values of the custom signals to be set for
* the app instance
*/
@NonNull
public Task<Void> setCustomSignals(@NonNull CustomSignals customSignals) {
return Tasks.call(
executor,
() -> {
frcSharedPrefs.setCustomSignals(customSignals.customSignals);
return null;
});
}

/**
* Notifies the Firebase A/B Testing SDK about activated experiments.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ fun remoteConfigSettings(
return builder.build()
}

fun customSignals(builder: CustomSignals.Builder.() -> Unit) =
CustomSignals.Builder().apply(builder).build()

/**
* Starts listening for config updates from the Remote Config backend and emits [ConfigUpdate]s via
* a [Flow]. See [FirebaseRemoteConfig.addOnConfigUpdateListener] for more information.
Expand Down
Loading
Loading