Skip to content

Commit

Permalink
feat(remote-config): support realtime config updates (#502)
Browse files Browse the repository at this point in the history
* Add definitions

* wip

* `addOnConfigUpdateListener` -> `addConfigUpdateListener`

* wip [skip ci]

* wip: android

* docs: add changeset [skip ci]

* docs [skip ci]

* wip: ios

* fix(ios): set `CAPPluginReturnCallback`
  • Loading branch information
robingenz committed Dec 14, 2023
1 parent 07d038a commit 6ea9328
Show file tree
Hide file tree
Showing 22 changed files with 609 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/fresh-panthers-speak.md
@@ -0,0 +1,5 @@
---
'@capacitor-firebase/remote-config': minor
---

feat: support realtime config updates
@@ -1,7 +1,5 @@
package io.capawesome.capacitorjs.plugins.firebase.firestore.interfaces;

import androidx.annotation.NonNull;

public interface ResultCallback {
void error(Exception exception);
}
103 changes: 103 additions & 0 deletions packages/remote-config/README.md
Expand Up @@ -68,6 +68,29 @@ const getString = async () => {
});
return value;
};

const addConfigUpdateListener = async () => {
const callbackId = await FirebaseRemoteConfig.addConfigUpdateListener(
(event, error) => {
if (error) {
console.error(error);
} else {
console.log(event);
}
}
);
return callbackId;
};

const removeConfigUpdateListener = async (callbackId: string) => {
await FirebaseRemoteConfig.removeConfigUpdateListener({
callbackId,
});
};

const removeAllListeners = async () => {
await FirebaseRemoteConfig.removeAllListeners();
};
```

## API
Expand All @@ -81,6 +104,9 @@ const getString = async () => {
* [`getNumber(...)`](#getnumber)
* [`getString(...)`](#getstring)
* [`setMinimumFetchInterval(...)`](#setminimumfetchinterval)
* [`addConfigUpdateListener(...)`](#addconfigupdatelistener)
* [`removeConfigUpdateListener(...)`](#removeconfigupdatelistener)
* [`removeAllListeners()`](#removealllisteners)
* [Interfaces](#interfaces)
* [Type Aliases](#type-aliases)
* [Enums](#enums)
Expand Down Expand Up @@ -209,6 +235,59 @@ Only available for Web.
--------------------


### addConfigUpdateListener(...)

```typescript
addConfigUpdateListener(callback: AddConfigUpdateListenerOptionsCallback) => Promise<CallbackId>
```

Add a listener for the config update event.

Only available for Android and iOS.

| Param | Type |
| -------------- | --------------------------------------------------------------------------------------------------------- |
| **`callback`** | <code><a href="#addconfigupdatelisteneroptionscallback">AddConfigUpdateListenerOptionsCallback</a></code> |

**Returns:** <code>Promise&lt;string&gt;</code>

**Since:** 5.4.0

--------------------


### removeConfigUpdateListener(...)

```typescript
removeConfigUpdateListener(options: RemoveConfigUpdateListenerOptions) => Promise<void>
```

Remove a listener for the config update event.

Only available for Android and iOS.

| Param | Type |
| ------------- | ----------------------------------------------------------------------------------------------- |
| **`options`** | <code><a href="#removeconfigupdatelisteneroptions">RemoveConfigUpdateListenerOptions</a></code> |

**Since:** 5.4.0

--------------------


### removeAllListeners()

```typescript
removeAllListeners() => Promise<void>
```

Remove all listeners for this plugin.

**Since:** 5.4.0

--------------------


### Interfaces


Expand Down Expand Up @@ -257,6 +336,20 @@ Only available for Web.
| **`minimumFetchIntervalInSeconds`** | <code>number</code> | Define the maximum age in seconds of an entry in the config cache before it is considered stale. During development, it's recommended to set a relatively low minimum fetch interval. | <code>43200</code> | 1.3.0 |


#### AddConfigUpdateListenerOptionsCallbackEvent

| Prop | Type | Description | Since |
| ----------------- | --------------------- | ---------------------------------------------------------------------------------- | ----- |
| **`updatedKeys`** | <code>string[]</code> | Parameter keys whose values have been updated from the currently activated values. | 5.4.0 |


#### RemoveConfigUpdateListenerOptions

| Prop | Type | Description | Since |
| -------- | ------------------------------------------------- | --------------------------------- | ----- |
| **`id`** | <code><a href="#callbackid">CallbackId</a></code> | The id of the listener to remove. | 5.4.0 |


### Type Aliases


Expand All @@ -275,6 +368,16 @@ Only available for Web.
<code><a href="#getoptions">GetOptions</a></code>


#### AddConfigUpdateListenerOptionsCallback

<code>(event: <a href="#addconfigupdatelisteneroptionscallbackevent">AddConfigUpdateListenerOptionsCallbackEvent</a> | null, error: any): void</code>


#### CallbackId

<code>string</code>


### Enums


Expand Down
@@ -1,13 +1,32 @@
package io.capawesome.capacitorjs.plugins.firebase.remoteconfig;

import static io.capawesome.capacitorjs.plugins.firebase.remoteconfig.FirebaseRemoteConfigPlugin.TAG;

import android.util.Log;
import androidx.annotation.NonNull;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.remoteconfig.ConfigUpdate;
import com.google.firebase.remoteconfig.ConfigUpdateListener;
import com.google.firebase.remoteconfig.ConfigUpdateListenerRegistration;
import com.google.firebase.remoteconfig.FirebaseRemoteConfigException;
import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue;
import io.capawesome.capacitorjs.plugins.firebase.remoteconfig.classes.events.AddConfigUpdateListenerOptionsCallbackEvent;
import io.capawesome.capacitorjs.plugins.firebase.remoteconfig.classes.options.AddConfigUpdateListenerOptions;
import io.capawesome.capacitorjs.plugins.firebase.remoteconfig.classes.options.RemoveConfigUpdateListenerOptions;
import io.capawesome.capacitorjs.plugins.firebase.remoteconfig.interfaces.NonEmptyResultCallback;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class FirebaseRemoteConfig {

private final FirebaseRemoteConfigPlugin plugin;
private final com.google.firebase.remoteconfig.FirebaseRemoteConfig remoteConfigInstance;
private final Map<String, ConfigUpdateListenerRegistration> listenerRegistrationMap = new HashMap<>();

public FirebaseRemoteConfig() {
public FirebaseRemoteConfig(FirebaseRemoteConfigPlugin plugin) {
this.plugin = plugin;
this.remoteConfigInstance = com.google.firebase.remoteconfig.FirebaseRemoteConfig.getInstance();
}

Expand All @@ -21,7 +40,7 @@ public void activate(final ActivateResultCallback resultCallback) {
)
.addOnFailureListener(
exception -> {
Log.w(FirebaseRemoteConfigPlugin.TAG, "Activate config failed.", exception);
Log.w(TAG, "Activate config failed.", exception);
resultCallback.error(exception.getMessage());
}
);
Expand All @@ -37,7 +56,7 @@ public void fetchAndActivate(final ActivateResultCallback resultCallback) {
)
.addOnFailureListener(
exception -> {
Log.w(FirebaseRemoteConfigPlugin.TAG, "Fetch and activate config failed.", exception);
Log.w(TAG, "Fetch and activate config failed.", exception);
resultCallback.error(exception.getMessage());
}
);
Expand All @@ -53,7 +72,7 @@ public void fetchConfig(long minimumFetchIntervalInSeconds, final FetchConfigRes
)
.addOnFailureListener(
exception -> {
Log.w(FirebaseRemoteConfigPlugin.TAG, "Fetch config failed.", exception);
Log.w(TAG, "Fetch config failed.", exception);
resultCallback.error(exception.getMessage());
}
);
Expand All @@ -73,4 +92,44 @@ public GetValueResult<String> getString(String key) {
FirebaseRemoteConfigValue value = remoteConfigInstance.getValue(key);
return new GetValueResult<String>(value.asString(), value.getSource());
}

public void addConfigUpdateListener(@NonNull AddConfigUpdateListenerOptions options, @NonNull NonEmptyResultCallback callback) {
String callbackId = options.getCallbackId();

ConfigUpdateListenerRegistration listenerRegistration =
this.remoteConfigInstance.addOnConfigUpdateListener(
new ConfigUpdateListener() {
@Override
public void onUpdate(ConfigUpdate configUpdate) {
AddConfigUpdateListenerOptionsCallbackEvent event = new AddConfigUpdateListenerOptionsCallbackEvent(
configUpdate
);
callback.success(event);
}

@Override
public void onError(FirebaseRemoteConfigException error) {
callback.error(error);
}
}
);
this.listenerRegistrationMap.put(callbackId, listenerRegistration);
}

public void removeConfigUpdateListener(@NonNull RemoveConfigUpdateListenerOptions options) {
String callbackId = options.getCallbackId();

ConfigUpdateListenerRegistration listenerRegistration = this.listenerRegistrationMap.get(callbackId);
if (listenerRegistration != null) {
listenerRegistration.remove();
}
this.listenerRegistrationMap.remove(callbackId);
}

public void removeAllListeners() {
for (ConfigUpdateListenerRegistration listenerRegistration : this.listenerRegistrationMap.values()) {
listenerRegistration.remove();
}
this.listenerRegistrationMap.clear();
}
}
Expand Up @@ -6,13 +6,28 @@
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
import io.capawesome.capacitorjs.plugins.firebase.remoteconfig.classes.options.AddConfigUpdateListenerOptions;
import io.capawesome.capacitorjs.plugins.firebase.remoteconfig.classes.options.RemoveConfigUpdateListenerOptions;
import io.capawesome.capacitorjs.plugins.firebase.remoteconfig.interfaces.NonEmptyResultCallback;
import io.capawesome.capacitorjs.plugins.firebase.remoteconfig.interfaces.Result;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

@CapacitorPlugin(name = "FirebaseRemoteConfig")
public class FirebaseRemoteConfigPlugin extends Plugin {

public static final String TAG = "FirebaseRemoteConfig";
public static final String ERROR_KEY_MISSING = "key must be provided.";
private FirebaseRemoteConfig implementation = new FirebaseRemoteConfig();
public static final String ERROR_CALLBACK_ID_MISSING = "callbackId must be provided.";

private Map<String, PluginCall> pluginCallMap = new HashMap<>();

private FirebaseRemoteConfig implementation;

public void load() {
implementation = new FirebaseRemoteConfig(this);
}

@PluginMethod
public void activate(PluginCall call) {
Expand Down Expand Up @@ -143,4 +158,74 @@ public void getString(PluginCall call) {
public void setMinimumFetchInterval(PluginCall call) {
call.reject("Not available on Android.");
}

@PluginMethod(returnType = PluginMethod.RETURN_CALLBACK)
public void addConfigUpdateListener(PluginCall call) {
try {
call.setKeepAlive(true);

String callbackId = call.getCallbackId();

this.pluginCallMap.put(callbackId, call);

AddConfigUpdateListenerOptions options = new AddConfigUpdateListenerOptions(callbackId);
NonEmptyResultCallback callback = new NonEmptyResultCallback() {
@Override
public void success(Result result) {
call.resolve(result.toJSObject());
}

@Override
public void error(Exception exception) {
Logger.error(TAG, exception.getMessage(), exception);
call.reject(exception.getMessage());
}
};

implementation.addConfigUpdateListener(options, callback);
} catch (Exception exception) {
Logger.error(TAG, exception.getMessage(), exception);
call.reject(exception.getMessage());
}
}

@PluginMethod
public void removeConfigUpdateListener(PluginCall call) {
try {
String callbackId = call.getString("callbackId");
if (callbackId == null) {
call.reject(ERROR_CALLBACK_ID_MISSING);
return;
}

PluginCall savedCall = this.pluginCallMap.remove(callbackId);
savedCall.release(this.bridge);

RemoveConfigUpdateListenerOptions options = new RemoveConfigUpdateListenerOptions(callbackId);
implementation.removeConfigUpdateListener(options);
call.resolve();
} catch (Exception exception) {
Logger.error(TAG, exception.getMessage(), exception);
call.reject(exception.getMessage());
}
}

@PluginMethod
public void removeAllListeners(PluginCall call) {
try {
Iterator<Map.Entry<String, PluginCall>> iterator = this.pluginCallMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, PluginCall> entry = iterator.next();
PluginCall savedCall = entry.getValue();
savedCall.release(this.bridge);
iterator.remove();
}

implementation.removeAllListeners();
super.removeAllListeners(call);
} catch (Exception exception) {
Logger.error(TAG, exception.getMessage(), exception);
call.reject(exception.getMessage());
}
}
}
@@ -0,0 +1,27 @@
package io.capawesome.capacitorjs.plugins.firebase.remoteconfig.classes.events;

import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
import com.google.firebase.remoteconfig.ConfigUpdate;
import io.capawesome.capacitorjs.plugins.firebase.remoteconfig.interfaces.Result;
import java.util.Set;

public class AddConfigUpdateListenerOptionsCallbackEvent implements Result {

private ConfigUpdate configUpdate;

public AddConfigUpdateListenerOptionsCallbackEvent(ConfigUpdate configUpdate) {
this.configUpdate = configUpdate;
}

public JSObject toJSObject() {
JSArray updatedKeysResult = new JSArray();
for (String key : configUpdate.getUpdatedKeys()) {
updatedKeysResult.put(key);
}

JSObject result = new JSObject();
result.put("updatedKeys", updatedKeysResult);
return result;
}
}

0 comments on commit 6ea9328

Please sign in to comment.