Skip to content

Commit 3e85862

Browse files
cynthiajoanCynthia Jianga-maurice
authored
Add support for Realtime RC (#690)
* init the listener change * callbacks pipeline setup and build * Remove ConfigUpdate, and general cleanup * Update RemoteConfigError.cs * Update RemoteConfigError.cs * Fix typo * Update readme.md * Use an IntPtr instead of the Internal class * Update FirebaseRemoteConfig.cs * Fix some comments * Update readme.md --------- Co-authored-by: Cynthia Jiang <[email protected]> Co-authored-by: a-maurice <[email protected]>
1 parent 19755d1 commit 3e85862

File tree

6 files changed

+196
-19
lines changed

6 files changed

+196
-19
lines changed

docs/readme.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ Release Notes
7272
### 11.0.0
7373
- Changes
7474
- Messaging: Remove deprecated calls `Send`, `Subscribe`, and `Unsubscribe`.
75+
- Remote Config (Android/iOS): Added support for real-time config updates. Use the new
76+
`OnConfigUpdateListener` API to get real-time updates. Existing
77+
`FetchAsync` and `ActivateAsync` APIs aren't affected by this change.
78+
To learn more, see
79+
[Get started with Firebase Remote Config](https://firebase.google.com/docs/remote-config/get-started?platform=unity#add-real-time-listener).
7580

7681
### 10.7.0
7782
- Changes

remote_config/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ set(firebase_remote_config_swig
2424
# Firebase RemoteConfig CSharp files
2525
set(firebase_remote_config_src
2626
src/ConfigSettings.cs
27+
src/ConfigUpdateEventArgs.cs
2728
src/ConfigValue.cs
2829
src/FirebaseRemoteConfig.cs
30+
src/RemoteConfigError.cs
2931
)
3032

3133
firebase_swig_add_library(firebase_remote_config_swig
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License. */
15+
16+
using System.Collections;
17+
using System.Collections.Generic;
18+
19+
namespace Firebase.RemoteConfig {
20+
// Object passed to ConfigUpdateEventHandlers that contains ConfigUpdate arguments.
21+
public sealed class ConfigUpdateEventArgs : System.EventArgs {
22+
// The keys that have changes in the config.
23+
public IEnumerable<string> UpdatedKeys { get; set; }
24+
25+
// Remote Config Errors that may have come up while listening for updates.
26+
public RemoteConfigError Error { get; set; }
27+
}
28+
}
29+

remote_config/src/FirebaseRemoteConfig.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,41 @@ public sealed class FirebaseRemoteConfig {
3434
// Key of this instance within remoteConfigByInstanceKey.
3535
private string instanceKey;
3636

37+
// Function for C++ to call when the config is updated.
38+
private static RemoteConfigUtil.ConfigUpdateDelegate configUpdateDelegate =
39+
new RemoteConfigUtil.ConfigUpdateDelegate(ConfigUpdateMethod);
40+
3741
/// @brief App object associated with this FirebaseRemoteConfig.
3842
public FirebaseApp App {
3943
get { return firebaseApp; }
4044
}
4145

46+
private event EventHandler<ConfigUpdateEventArgs> ConfigUpdateListenerImpl;
47+
// EventHandlers that will be used as ConfigUpdateListeners. The first listener added will open
48+
// the stream connection and the last removed will close the stream.
49+
public event EventHandler<ConfigUpdateEventArgs> OnConfigUpdateListener {
50+
add {
51+
ThrowIfNull();
52+
// If this is the first listener, hook into C++.
53+
if (ConfigUpdateListenerImpl == null ||
54+
ConfigUpdateListenerImpl.GetInvocationList().Length == 0) {
55+
RemoteConfigUtil.SetConfigUpdateCallback(remoteConfigInternal, configUpdateDelegate);
56+
}
57+
58+
ConfigUpdateListenerImpl += value;
59+
}
60+
remove {
61+
ThrowIfNull();
62+
ConfigUpdateListenerImpl -= value;
63+
64+
// If that was the last listener, remove the C++ hooks.
65+
if (ConfigUpdateListenerImpl == null ||
66+
ConfigUpdateListenerImpl.GetInvocationList().Length == 0) {
67+
RemoteConfigUtil.SetConfigUpdateCallback(remoteConfigInternal, null);
68+
}
69+
}
70+
}
71+
4272
private FirebaseRemoteConfig(FirebaseRemoteConfigInternal remoteConfig, FirebaseApp app) {
4373
firebaseApp = app;
4474
firebaseApp.AppDisposed += OnAppDisposed;
@@ -313,5 +343,31 @@ public static TimeSpan DefaultCacheExpiration {
313343
public static ulong DefaultTimeoutInMilliseconds {
314344
get { return RemoteConfigUtil.kDefaultTimeoutInMilliseconds; }
315345
}
346+
347+
internal void OnConfigUpdate(ConfigUpdateInternal configUpdate, RemoteConfigError error) {
348+
EventHandler<ConfigUpdateEventArgs> handler = ConfigUpdateListenerImpl;
349+
if (handler != null) {
350+
// Make a copy of the list, to not rely on the Swig list
351+
List<string> updatedKeys = new List<string>(configUpdate.updated_keys);
352+
handler(this, new ConfigUpdateEventArgs {
353+
UpdatedKeys = updatedKeys,
354+
Error = error
355+
});
356+
}
357+
}
358+
359+
[MonoPInvokeCallback(typeof(RemoteConfigUtil.ConfigUpdateDelegate))]
360+
private static void ConfigUpdateMethod(string appName, System.IntPtr configUpdatePtr,
361+
int error) {
362+
FirebaseRemoteConfig rc;
363+
if (remoteConfigByInstanceKey.TryGetValue(appName, out rc)) {
364+
// Create a ConfigUpdateInternal with the given pointer
365+
ConfigUpdateInternal configUpdate = new ConfigUpdateInternal(configUpdatePtr, false);
366+
// convert error to RemoteConfigError
367+
RemoteConfigError errorInternal = (RemoteConfigError)error;
368+
369+
rc.OnConfigUpdate(configUpdate, errorInternal);
370+
}
371+
}
316372
}
317373
} // namespace Firebase.RemoteConfig
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License. */
15+
16+
namespace Firebase.RemoteConfig {
17+
/// <summary>
18+
/// Describes the error codes returned by Remote Config.
19+
/// </summary>
20+
public enum RemoteConfigError {
21+
// Unimplemented error found.
22+
Unimplemented = -1,
23+
// No error.
24+
None = 0,
25+
// Unable to make a connection to the Remote Config backend.
26+
ConfigUpdateStreamError,
27+
// The ConfigUpdate message was unparsable.
28+
ConfigUpdateMessageInvalid,
29+
// Unable to fetch the latest version of the config.
30+
ConfigUpdateNotFetched,
31+
// The Remote Config real-time config update service is unavailable.
32+
ConfigUpdateUnavailable,
33+
}
34+
}

remote_config/src/swig/remote_config.i

Lines changed: 70 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,31 @@
2727
%include "app/src/swig/serial_dispose.i"
2828
%include "app/src/swig/future.i"
2929

30+
// Start of code added to the C++ module file
3031
%{
3132
#include <algorithm>
3233
#include <map>
3334
#include <vector>
35+
#include "app/src/callback.h"
3436
#include "app/src/cpp_instance_manager.h"
3537
#include "remote_config/src/include/firebase/remote_config.h"
3638

3739
namespace firebase {
3840
namespace remote_config {
3941

42+
// Declare the C++ callback delegate equivalent to C# RemoteConfigUtil.ConfigUpdateDelegate
43+
typedef void (SWIGSTDCALL *ConfigUpdateListener)(const char* app_name, ConfigUpdate* config_update, int error);
44+
4045
// Reference count manager for C++ RemoteConfig instance, using pointer as the
4146
// key for searching.
4247
static CppInstanceManager<RemoteConfig> g_rc_instances;
4348

49+
// App -> C++ ConfigUpdateListener map.
50+
static std::map<App*, firebase::remote_config::ConfigUpdateListenerRegistration> g_registered_listeners;
51+
52+
// Should be set to the C# function FirebaseRemoteConfig.ConfigUpdateMethod
53+
static ConfigUpdateListener g_config_updated = nullptr;
54+
4455
// Internal struct used to pass the Remote Config stored data from C++ to C#.
4556
struct ConfigValueInternal {
4657
// The raw data.
@@ -49,36 +60,61 @@ struct ConfigValueInternal {
4960
ValueSource source;
5061
};
5162

63+
// Wrapper that calls g_config_updated. Should be used with the
64+
// callback logic to guarantee it is on the Unity thread.
65+
static void CallConfigUpdate(ConfigUpdate cu, RemoteConfigError error, const char* name) {
66+
if (g_config_updated) {
67+
// Should be calling FirebaseRemoteConfig.ConfigUpdateMethod
68+
g_config_updated(name, &cu, static_cast<int>(error));
69+
}
70+
}
71+
// Called by C# to register a RemoteConfig instance for config updates,
72+
// provided via the callback method provided.
73+
void SetConfigUpdateCallback(RemoteConfig* rc, firebase::remote_config::ConfigUpdateListener on_config_updated) {
74+
// If given a method, save it, and add a new listener for Config Updates.
75+
if (on_config_updated) {
76+
if (!g_config_updated) {
77+
g_config_updated = on_config_updated;
78+
}
79+
std::string app_name = rc->app()->name();
80+
ConfigUpdateListenerRegistration registration = rc->AddOnConfigUpdateListener([app_name](ConfigUpdate&& cu, RemoteConfigError error) {
81+
// Queue up a call to g_config_updated
82+
ConfigUpdate cuLocal = std::move(cu);
83+
firebase::callback::AddCallback(
84+
new firebase::callback::CallbackValue2String1<ConfigUpdate, RemoteConfigError>(
85+
cuLocal, error, app_name.c_str(), CallConfigUpdate));
86+
});
87+
88+
// Save the registration in a map so that it can be removed later if needed.
89+
g_registered_listeners[rc->app()] = registration;
90+
} else {
91+
// Remove the listener, and cleanup the callback if no more remain.
92+
ConfigUpdateListenerRegistration registration = g_registered_listeners[rc->app()];
93+
g_registered_listeners.erase(rc->app());
94+
registration.Remove();
95+
96+
if (g_registered_listeners.empty()) {
97+
g_config_updated = nullptr;
98+
}
99+
}
100+
}
101+
52102
} // remote_config
53103
} // firebase
54-
%}
104+
105+
%} // End of code added to the C++ module file
55106

56107
// All outputs of CharVector should instead be IEnumerable<byte>
57108
%typemap(cstype, out="global::System.Collections.Generic.IEnumerable<byte>") std::vector<unsigned char> "CharVector";
58109
// All outputs of StringList should instead be IEnumerable<string>
59110
%typemap(cstype, out="global::System.Collections.Generic.IEnumerable<string>") std::vector<std::string> "StringList";
60111

61-
// Ignore all the deprecated static functions
62-
%ignore firebase::remote_config::Initialize;
63-
%ignore firebase::remote_config::Terminate;
64-
%ignore firebase::remote_config::SetDefaults;
65-
%ignore firebase::remote_config::GetConfigSetting;
66-
%ignore firebase::remote_config::SetConfigSetting;
67-
%ignore firebase::remote_config::GetBoolean;
68-
%ignore firebase::remote_config::GetLong;
69-
%ignore firebase::remote_config::GetDouble;
70-
%ignore firebase::remote_config::GetString;
71-
%ignore firebase::remote_config::GetData;
72-
%ignore firebase::remote_config::GetKeysByPrefix;
73-
%ignore firebase::remote_config::GetKeys;
74-
%ignore firebase::remote_config::Fetch;
75-
%ignore firebase::remote_config::ActivateFetched;
76-
%ignore firebase::remote_config::GetInfo;
77-
78112
// Ignore unused structs and enums
79113
%ignore firebase::remote_config::ValueInfo;
80114
%ignore firebase::remote_config::ConfigKeyValue;
81115
%ignore firebase::remote_config::ConfigSetting;
116+
%ignore firebase::remote_config::RemoteConfigError;
117+
%ignore firebase::remote_config::ConfigUpdateListenerRegistration;
82118

83119
// Configure the ConfigInfo class
84120
%csmethodmodifiers fetch_time "internal";
@@ -131,6 +167,8 @@ struct ConfigValueInternal {
131167

132168
%rename (ConfigSettingsInternal) firebase::remote_config::ConfigSettings;
133169

170+
%rename (ConfigUpdateInternal) firebase::remote_config::ConfigUpdate;
171+
134172
// Configure properties for get / set methods on the FirebaseRemoteConfigInternal class.
135173
%attribute(firebase::remote_config::RemoteConfig, firebase::App*, App, app);
136174

@@ -147,6 +185,7 @@ struct ConfigValueInternal {
147185
%ignore firebase::remote_config::RemoteConfig::GetData;
148186
// Ignore GetAll, as we do not rely on the C++ function for it.
149187
%ignore firebase::remote_config::RemoteConfig::GetAll;
188+
%ignore firebase::remote_config::RemoteConfig::AddOnConfigUpdateListener;
150189

151190
%typemap(cscode) firebase::remote_config::RemoteConfig %{
152191
// Generates a key that uniquely identifies this instance.
@@ -176,8 +215,13 @@ struct ConfigValueInternal {
176215
System.GC.SuppressFinalize(this);
177216
}
178217

218+
SWIG_MAP_CFUNC_TO_CSDELEGATE(
219+
firebase::remote_config::ConfigUpdateListener,
220+
Firebase.RemoteConfig.RemoteConfigUtil.ConfigUpdateDelegate)
221+
179222
%include "remote_config/src/include/firebase/remote_config.h"
180223

224+
// Start of C++ definitions that Swig needs to know of, to generate C# for
181225
namespace firebase {
182226
namespace remote_config {
183227

@@ -186,10 +230,17 @@ struct ConfigValueInternal {
186230
ValueSource source;
187231
};
188232

233+
void SetConfigUpdateCallback(firebase::remote_config::RemoteConfig* rc,
234+
firebase::remote_config::ConfigUpdateListener config_listener);
235+
189236
}
190-
}
237+
} // End of C++ definitions
238+
191239

192240
%pragma(csharp) modulecode=%{
241+
242+
internal delegate void ConfigUpdateDelegate(string appName, System.IntPtr configUpdatePtr, int error);
243+
193244
/// The C++ mapping requires a StringStringMap, so convert the more generic
194245
/// dictionary into that map.
195246
internal static StringStringMap ConvertDictionaryToMap(

0 commit comments

Comments
 (0)