diff --git a/docs/migration-guide.mdx b/docs/migration-guide.mdx index 3365a4fd3d21..8e9758600d0e 100644 --- a/docs/migration-guide.mdx +++ b/docs/migration-guide.mdx @@ -12,15 +12,16 @@ to upgrade to the latest recommended versions, follow this guide to help ease th The table below shows the recommended package versions for each plugin this migration guide covers. -| Plugin | Version | -| ---------------------: | ------------------------------------: | -| `cloud_firestore` | `^{{ plugins.cloud_firestore }}` | -| `cloud_functions` | `^{{ plugins.cloud_functions }}` | -| `firebase_auth` | `^{{ plugins.firebase_auth }}` | -| `firebase_core` | `^{{ plugins.firebase_core }}` | -| `firebase_crashlytics` | `^{{ plugins.firebase_crashlytics }}` | -| `firebase_storage` | `^{{ plugins.firebase_storage }}` | -| `firebase_messaging` | `^{{ plugins.firebase_messaging }}` | +| Plugin | Version | +| ----------------------: | ---------------------------------------: | +| `cloud_firestore` | `^{{ plugins.cloud_firestore }}` | +| `cloud_functions` | `^{{ plugins.cloud_functions }}` | +| `firebase_auth` | `^{{ plugins.firebase_auth }}` | +| `firebase_core` | `^{{ plugins.firebase_core }}` | +| `firebase_crashlytics` | `^{{ plugins.firebase_crashlytics }}` | +| `firebase_storage` | `^{{ plugins.firebase_storage }}` | +| `firebase_messaging` | `^{{ plugins.firebase_messaging }}` | +|`firebase_remote_config` | `^{{ plugins.firebase_remote_config }}` | ## Breaking Changes & Deprecations @@ -62,6 +63,7 @@ dependencies: cloud_functions: "^{{ plugins.cloud_functions }}" firebase_storage: "^{{ plugins.firebase_storage }}" firebase_messaging: "^{{ plugins.firebase_messaging }}" + firebase_remote_config: "^{{ plugins.firebase_remote_config }}" # Updated to work with new core only plugins (no new changes): firebase_admob: "^{{ plugins.firebase_admob }}" firebase_analytics: "^{{ plugins.firebase_analytics }}" @@ -69,7 +71,6 @@ dependencies: firebase_dynamic_links: "^{{ plugins.firebase_dynamic_links }}" firebase_in_app_messaging: "^{{ plugins.firebase_in_app_messaging }}" firebase_performance: "^{{ plugins.firebase_performance }}" - firebase_remote_config: "^{{ plugins.firebase_remote_config }}" ``` Install the updated plugins by running the following command from the project root: @@ -566,6 +567,33 @@ We've also added lots of features which can be seen in the changelog below, howe - `BackgroundMessageHandler`, `AppleNotificationSetting`, `AppleShowPreviewSetting`, `AuthorizationStatus` , `AndroidNotificationPriority`, `AndroidNotificationVisibility` +### Remote Config + +The plugin has been updated and reworked to better mirror the features +currently offered by the native (iOS and Android) clients. + +`RemoteConfig`: + - **CHORE**: migrate to platform interface. + - **FEAT**: support multiple firebase apps. `RemoteConfig.instanceFor()` can + be used to retrieve an instance of RemoteConfig for a particular + Firebase App. + - **BREAKING**: `fetch()` now takes no arguments. `RemoteConfigSettings` should + be used to specify the freshness of the cached config via the `minimumFetchInterval` + property. + - **BREAKING**: `activateFetched()` is now `activate()`. + - **FEAT**: Added `fetchAndActivate()` support. + - **FEAT**: Added `ensureInitialized()` support. + +`RemoteConfigSettings` + - **BREAKING**: `fetchTimeoutMillis` is now `fetchTimeout`. + - **BREAKING**: `minimumFetchIntervalMillis` is now `minimumFetchInterval` + - **BREAKING**: `fetchTimeout` and `minimumFetchInterval` are refactored + from `int` to `Duration`. + +`FetchThrottledException` + - **BREAKING**: removed `FetchThrottledException`. The general + FirebaseException is used to handle all RemoteConfig specific exceptions. + ### Storage Overall, Firebase Storage has been heavily reworked to bring it inline with the federated plugin setup along with adding diff --git a/docs/remote-config/usage.mdx b/docs/remote-config/usage.mdx index d82c00e21dd2..fc6c2439e6eb 100644 --- a/docs/remote-config/usage.mdx +++ b/docs/remote-config/usage.mdx @@ -1,6 +1,83 @@ --- -title: Remote Config +title: Using Firebase Remote Config sidebar_label: Usage --- -Remote Config usage +Once installed, you can access the [`firebase_remote_config`](!firebase_remote_config) plugin by importing +it in your Dart code: + +```dart +import 'package:firebase_remote_config/firebase_remote_config.dart'; +``` + +Before using Firebase Remote Config, you must first have ensured you have [initialized FlutterFire](../overview.mdx#initializing-flutterfire). + +To create a new Firebase Remote Config instance, call the [`instance`](!firebase_remote_config.FirebaseRemoteConfig.instance) +getter on [`FirebaseRemoteConfig`](!firebase_remote_config.FirebaseRemoteConfig): + +```dart +FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.instance; +``` + +By default, this allows you to interact with Firebase Remote Config using the default Firebase App used whilst installing FlutterFire on your +platform. If however you'd like to use a secondary Firebase App, use the [`instanceFor`](!firebase_remote_config.FirebaseRemoteConfig.instanceFor) method: + +```dart +FirebaseApp secondaryApp = Firebase.app('SecondaryApp'); +FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.instanceFor(app: secondaryApp); +``` + +## Defaults + +Firebase Remote Config default parameters allows your application to startup without the need to make a +network call. To set the default parameters call the `setDefaults()` method on your [`FirebaseRemoteConfig`](!firebase_remote_config.FirebaseRemoteConfig) instance: + +```dart +remoteConfig.setDefaults({ + 'welcome_message': 'this is the default welcome message', + 'feat1_enabled': false, +}); +``` + +## Fetching and activating + +Only when default parameters are no longer sufficient updates to the Remote Config template should be made +in the Firebase console or via the Admin SDK. Use the `fetchAndActivate()` method on your [`FirebaseRemoteConfig`](!firebase_remote_config.FirebaseRemoteConfig) instance: + +```dart +bool updated = await remoteConfig.fetchAndActivate(); +if (updated) { + // the config has been updated, new parameter values are available. +} else { + // the config values were previously updated. +} +``` + +The `fetchAndActivate()` combines the operations of the `fetch()` and `activate()` methods which can be used separately +if needed. + +## Get/Use parameters + +Whether using default or fetched parameters, you can fetch the parameter values using the available getters: + +```dart +Text('Welcome ${remoteConfig.getString('welcome_message')}') +``` + +Other getters include: `getBool()`, `getInt()`, `getDouble()`, `getValue()`. + +## Settings + +Since fetching involves network calls Remote Config allows you to specify how long cached parameters are valid and +how long a fetch should wait before timing out. These settings can be specified using the `setConfigSettings()` +method on your [`FirebaseRemoteConfig`](!firebase_remote_config.FirebaseRemoteConfig) instance: + +```dart +await remoteConfig.setConfigSettings(RemoteConfigSettings( + fetchTimeout: Duration(seconds: 10), + minimumFetchInterval: Duration(hours: 1), +)); +``` + +The above snippet specifies that a fetch will wait up to 10 seconds before timing out and fetch parameters will be +cached for a maximum of 1 hour before being considered stale and going to the server on the next `fetch()` call. diff --git a/docs/sidebars.js b/docs/sidebars.js index 15e374394033..6c9c6407a8f6 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -68,6 +68,7 @@ module.exports = { // "ML Kit Vision": ["ml-vision/usage", toReferenceAPI("firebase_ml_vision")], "Remote Config": [ "remote-config/overview", + "remote-config/usage", toReferenceAPI("firebase_remote_config"), ], "Performance Monitoring": [ diff --git a/packages/firebase_remote_config/README.md b/packages/firebase_remote_config/README.md deleted file mode 100644 index 12ca58516a7a..000000000000 --- a/packages/firebase_remote_config/README.md +++ /dev/null @@ -1,79 +0,0 @@ -# firebase_remote_config plugin - -A Flutter plugin to use the [Firebase Remote Config API](https://firebase.google.com/products/remote-config/). - -[![pub package](https://img.shields.io/pub/v/firebase_remote_config.svg)](https://pub.dev/packages/firebase_remote_config) - -For Flutter plugins for other Firebase products, see [README.md](https://github.com/FirebaseExtended/flutterfire/blob/master/README.md). - -## Usage - -### Import the firebase_remote_config plugin -To use the firebase_remote_config plugin, follow the [plugin installation instructions](https://pub.dev/packages/firebase_remote_config#pub-pkg-tab-installing). - -### Android integration - -Enable the Google services by configuring the Gradle scripts as such. - -1. Add the classpath to the `[project]/android/build.gradle` file. -```gradle -dependencies { - // Example existing classpath - classpath 'com.android.tools.build:gradle:3.5.4' - // Add the google services classpath - classpath 'com.google.gms:google-services:4.3.4' -} -``` - -2. Add the apply plugin to the `[project]/android/app/build.gradle` file. -```gradle -// ADD THIS AT THE BOTTOM -apply plugin: 'com.google.gms.google-services' -``` - -*Note:* If this section is not completed you will get an error like this: -``` -java.lang.IllegalStateException: -Default FirebaseApp is not initialized in this process [package name]. -Make sure to call FirebaseApp.initializeApp(Context) first. -``` - -*Note:* When you are debugging on android, use a device or AVD with Google Play services. -Otherwise you will not be able to use Firebase Remote Config. - -### Use the plugin - -Add the following imports to your Dart code: -```dart -import 'package:firebase_remote_config/firebase_remote_config.dart'; -``` - -Initialize `RemoteConfig`: -```dart -final RemoteConfig remoteConfig = await RemoteConfig.instance; -``` - -You can now use the Firebase `remoteConfig` to fetch remote configurations in your Dart code, e.g. -```dart -final defaults = {'welcome': 'default welcome'}; -await remoteConfig.setDefaults(defaults); - -await remoteConfig.fetch(expiration: const Duration(hours: 5)); -await remoteConfig.activateFetched(); -print('welcome message: ' + remoteConfig.getString('welcome')); -``` - -## Example - -See the [example application](https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_remote_config/example) source -for a complete sample app using the Firebase Remote Config. - -## Issues and feedback - -Please file FlutterFire specific issues, bugs, or feature requests in our [issue tracker](https://github.com/FirebaseExtended/flutterfire/issues/new). - -Plugin issues that are not specific to Flutterfire can be filed in the [Flutter issue tracker](https://github.com/flutter/flutter/issues/new). - -To contribute a change to this plugin, -please review our [contribution guide](https://github.com/FirebaseExtended/flutterfire/blob/master/CONTRIBUTING.md) -and open a [pull request](https://github.com/FirebaseExtended/flutterfire/pulls). diff --git a/packages/firebase_remote_config/analysis_options.yaml b/packages/firebase_remote_config/analysis_options.yaml deleted file mode 100644 index d4ccef63f1d1..000000000000 --- a/packages/firebase_remote_config/analysis_options.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# This is a temporary file to allow us to land a new set of linter rules in a -# series of manageable patches instead of one gigantic PR. It disables some of -# the new lints that are already failing on this plugin, for this plugin. It -# should be deleted and the failing lints addressed as soon as possible. - -include: ../../analysis_options.yaml - -analyzer: - errors: - public_member_api_docs: ignore - unawaited_futures: ignore diff --git a/packages/firebase_remote_config/android/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfig/FirebaseRemoteConfigPlugin.java b/packages/firebase_remote_config/android/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfig/FirebaseRemoteConfigPlugin.java deleted file mode 100644 index a6fe7c607adf..000000000000 --- a/packages/firebase_remote_config/android/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfig/FirebaseRemoteConfigPlugin.java +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.firebase.firebaseremoteconfig; - -import android.content.Context; -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.PluginRegistry.Registrar; - -/** FirebaseRemoteConfigPlugin */ -public class FirebaseRemoteConfigPlugin implements FlutterPlugin { - - static final String TAG = "FirebaseRemoteConfigPlugin"; - static final String PREFS_NAME = - "io.flutter.plugins.firebase.firebaseremoteconfig.FirebaseRemoteConfigPlugin"; - static final String METHOD_CHANNEL = "plugins.flutter.io/firebase_remote_config"; - - private MethodChannel channel; - - public static void registerWith(Registrar registrar) { - FirebaseRemoteConfigPlugin plugin = new FirebaseRemoteConfigPlugin(); - plugin.setupChannel(registrar.messenger(), registrar.context()); - } - - @Override - public void onAttachedToEngine(FlutterPluginBinding binding) { - setupChannel(binding.getBinaryMessenger(), binding.getApplicationContext()); - } - - @Override - public void onDetachedFromEngine(FlutterPluginBinding binding) { - tearDownChannel(); - } - - private void setupChannel(BinaryMessenger messenger, Context context) { - MethodCallHandlerImpl handler = - new MethodCallHandlerImpl(context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)); - channel = new MethodChannel(messenger, METHOD_CHANNEL); - channel.setMethodCallHandler(handler); - } - - private void tearDownChannel() { - channel.setMethodCallHandler(null); - channel = null; - } -} diff --git a/packages/firebase_remote_config/CHANGELOG.md b/packages/firebase_remote_config/firebase_remote_config/CHANGELOG.md similarity index 81% rename from packages/firebase_remote_config/CHANGELOG.md rename to packages/firebase_remote_config/firebase_remote_config/CHANGELOG.md index 9d147681d5f3..9a1866b02093 100644 --- a/packages/firebase_remote_config/CHANGELOG.md +++ b/packages/firebase_remote_config/firebase_remote_config/CHANGELOG.md @@ -1,3 +1,30 @@ +## 0.7.0 + +The plugin has been updated and reworked to better mirror the features +currently offered by the native (iOS and Android) clients. + +`RemoteConfig`: +- **CHORE**: migrate to platform interface. +- **FEAT**: support multiple firebase apps. `RemoteConfig.instanceFor()` can + be used to retrieve an instance of RemoteConfig for a particular + Firebase App. +- **BREAKING**: `fetch()` now takes no arguments. `RemoteConfigSettings` should + be used to specify the freshness of the cached config via the `minimumFetchInterval` + property. +- **BREAKING**: `activateFetched()` is now `activate()`. +- **FEAT**: Added `fetchAndActivate()` support. +- **FEAT**: Added `ensureInitialized()` support. + +`RemoteConfigSettings` +- **BREAKING**: `fetchTimeoutMillis` is now `fetchTimeout`. +- **BREAKING**: `minimumFetchIntervalMillis` is now `minimumFetchInterval` +- **BREAKING**: `fetchTimeout` and `minimumFetchInterval` are refactored + from `int` to `Duration`. + +`FetchThrottledException` +- **BREAKING**: removed `FetchThrottledException`. The general + FirebaseException is used to handle all RemoteConfig specific exceptions. + ## 0.6.0 > Note: This release has breaking changes. diff --git a/packages/firebase_remote_config/LICENSE b/packages/firebase_remote_config/firebase_remote_config/LICENSE similarity index 100% rename from packages/firebase_remote_config/LICENSE rename to packages/firebase_remote_config/firebase_remote_config/LICENSE diff --git a/packages/firebase_remote_config/firebase_remote_config/README.md b/packages/firebase_remote_config/firebase_remote_config/README.md new file mode 100644 index 000000000000..e6a9a59275d9 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config/README.md @@ -0,0 +1,26 @@ +# Firebase Remote Config Plugin for Flutter + +A Flutter plugin to use the [Firebase Remote Config API](https://firebase.google.com/docs/remote-config). + +To learn more about Firebase Remote Config, please visit the [Firebase website](https://firebase.google.com/products/remote-config) + +[![pub package](https://img.shields.io/pub/v/firebase_remote_config.svg)](https://pub.dev/packages/firebase_remote_config) + +## Getting Started + +To get started with Firebase Remote Config for Flutter, please [see the documentation](https://firebase.flutter.dev/docs/remote-config/overview) available +at [https://firebase.flutter.dev](https://firebase.flutter.dev) + +## Usage + +To use this plugin, please visit the [Firebase Remote Config Usage documentation](https://firebase.flutter.dev/docs/remote-config/usage) + +## Issues and feedback + +Please file FlutterFire specific issues, bugs, or feature requests in our [issue tracker](https://github.com/FirebaseExtended/flutterfire/issues/new). + +Plugin issues that are not specific to FlutterFire can be filed in the [Flutter issue tracker](https://github.com/flutter/flutter/issues/new). + +To contribute a change to this plugin, +please review our [contribution guide](https://github.com/FirebaseExtended/flutterfire/blob/master/CONTRIBUTING.md) +and open a [pull request](https://github.com/FirebaseExtended/flutterfire/pulls). diff --git a/packages/firebase_remote_config/android/build.gradle b/packages/firebase_remote_config/firebase_remote_config/android/build.gradle similarity index 93% rename from packages/firebase_remote_config/android/build.gradle rename to packages/firebase_remote_config/firebase_remote_config/android/build.gradle index 84349c4f9845..ce651758716b 100644 --- a/packages/firebase_remote_config/android/build.gradle +++ b/packages/firebase_remote_config/firebase_remote_config/android/build.gradle @@ -36,7 +36,10 @@ def getRootProjectExtOrCoreProperty(name, firebaseCoreProject) { android { compileSdkVersion 29 - + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } defaultConfig { minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/packages/firebase_remote_config/android/gradle.properties b/packages/firebase_remote_config/firebase_remote_config/android/gradle.properties similarity index 100% rename from packages/firebase_remote_config/android/gradle.properties rename to packages/firebase_remote_config/firebase_remote_config/android/gradle.properties diff --git a/packages/firebase_remote_config/android/settings.gradle b/packages/firebase_remote_config/firebase_remote_config/android/settings.gradle similarity index 100% rename from packages/firebase_remote_config/android/settings.gradle rename to packages/firebase_remote_config/firebase_remote_config/android/settings.gradle diff --git a/packages/firebase_remote_config/android/src/main/AndroidManifest.xml b/packages/firebase_remote_config/firebase_remote_config/android/src/main/AndroidManifest.xml similarity index 100% rename from packages/firebase_remote_config/android/src/main/AndroidManifest.xml rename to packages/firebase_remote_config/firebase_remote_config/android/src/main/AndroidManifest.xml diff --git a/packages/firebase_remote_config/firebase_remote_config/android/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfig/FirebaseRemoteConfigPlugin.java b/packages/firebase_remote_config/firebase_remote_config/android/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfig/FirebaseRemoteConfigPlugin.java new file mode 100644 index 000000000000..53865542c0c3 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config/android/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfig/FirebaseRemoteConfigPlugin.java @@ -0,0 +1,229 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.firebase.firebaseremoteconfig; + +import static io.flutter.plugins.firebase.core.FlutterFirebasePluginRegistry.registerPlugin; + +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.Tasks; +import com.google.firebase.FirebaseApp; +import com.google.firebase.remoteconfig.FirebaseRemoteConfig; +import com.google.firebase.remoteconfig.FirebaseRemoteConfigClientException; +import com.google.firebase.remoteconfig.FirebaseRemoteConfigFetchThrottledException; +import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings; +import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue; +import io.flutter.Log; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.PluginRegistry.Registrar; +import io.flutter.plugins.firebase.core.FlutterFirebasePlugin; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** FirebaseRemoteConfigPlugin */ +public class FirebaseRemoteConfigPlugin + implements FlutterFirebasePlugin, MethodChannel.MethodCallHandler, FlutterPlugin { + + static final String TAG = "FRCPlugin"; + static final String METHOD_CHANNEL = "plugins.flutter.io/firebase_remote_config"; + + private MethodChannel channel; + + public static void registerWith(Registrar registrar) { + FirebaseRemoteConfigPlugin plugin = new FirebaseRemoteConfigPlugin(); + plugin.setupChannel(registrar.messenger()); + } + + @Override + public void onAttachedToEngine(FlutterPluginBinding binding) { + setupChannel(binding.getBinaryMessenger()); + } + + @Override + public void onDetachedFromEngine(FlutterPluginBinding binding) { + tearDownChannel(); + } + + @Override + public Task> getPluginConstantsForFirebaseApp(final FirebaseApp firebaseApp) { + FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.getInstance(firebaseApp); + return Tasks.call( + cachedThreadPool, + () -> { + Map configProperties = getConfigProperties(remoteConfig); + Map configValues = new HashMap<>(); + configValues.putAll(configProperties); + configValues.put("parameters", parseParameters(remoteConfig.getAll())); + return configValues; + }); + } + + private Map getConfigProperties(FirebaseRemoteConfig remoteConfig) { + Map configProperties = new HashMap<>(); + configProperties.put( + "fetchTimeout", remoteConfig.getInfo().getConfigSettings().getFetchTimeoutInSeconds()); + configProperties.put( + "minimumFetchInterval", + remoteConfig.getInfo().getConfigSettings().getMinimumFetchIntervalInSeconds()); + configProperties.put("lastFetchTime", remoteConfig.getInfo().getFetchTimeMillis()); + configProperties.put( + "lastFetchStatus", mapLastFetchStatus(remoteConfig.getInfo().getLastFetchStatus())); + Log.d(TAG, "Sending fetchTimeout: " + configProperties.get("fetchTimeout")); + return configProperties; + } + + @Override + public Task didReinitializeFirebaseCore() { + return Tasks.call(() -> null); + } + + private void setupChannel(BinaryMessenger messenger) { + registerPlugin(METHOD_CHANNEL, this); + channel = new MethodChannel(messenger, METHOD_CHANNEL); + channel.setMethodCallHandler(this); + } + + private void tearDownChannel() { + channel.setMethodCallHandler(null); + channel = null; + } + + private FirebaseRemoteConfig getRemoteConfig(Map arguments) { + String appName = (String) Objects.requireNonNull(arguments.get("appName")); + FirebaseApp app = FirebaseApp.getInstance(appName); + return FirebaseRemoteConfig.getInstance(app); + } + + @Override + public void onMethodCall(MethodCall call, final MethodChannel.Result result) { + Task methodCallTask; + FirebaseRemoteConfig remoteConfig = getRemoteConfig(call.arguments()); + + switch (call.method) { + case "RemoteConfig#ensureInitialized": + { + methodCallTask = remoteConfig.ensureInitialized(); + break; + } + case "RemoteConfig#activate": + { + methodCallTask = remoteConfig.activate(); + break; + } + case "RemoteConfig#getAll": + { + methodCallTask = Tasks.forResult(parseParameters(remoteConfig.getAll())); + break; + } + case "RemoteConfig#fetch": + { + methodCallTask = remoteConfig.fetch(); + break; + } + case "RemoteConfig#fetchAndActivate": + { + methodCallTask = remoteConfig.fetchAndActivate(); + break; + } + case "RemoteConfig#setConfigSettings": + { + int fetchTimeout = call.argument("fetchTimeout"); + int minimumFetchInterval = call.argument("minimumFetchInterval"); + FirebaseRemoteConfigSettings settings = + new FirebaseRemoteConfigSettings.Builder() + .setFetchTimeoutInSeconds(fetchTimeout) + .setMinimumFetchIntervalInSeconds(minimumFetchInterval) + .build(); + methodCallTask = remoteConfig.setConfigSettingsAsync(settings); + break; + } + case "RemoteConfig#setDefaults": + { + Map defaults = call.argument("defaults"); + methodCallTask = remoteConfig.setDefaultsAsync(defaults); + break; + } + case "RemoteConfig#getProperties": + { + Map configProperties = getConfigProperties(remoteConfig); + methodCallTask = Tasks.forResult(configProperties); + break; + } + default: + { + result.notImplemented(); + return; + } + } + + methodCallTask.addOnCompleteListener( + task -> { + if (task.isSuccessful()) { + result.success(task.getResult()); + } else { + Exception exception = task.getException(); + Map details = new HashMap<>(); + if (exception instanceof FirebaseRemoteConfigFetchThrottledException) { + details.put("code", "throttled"); + details.put("message", "frequency of requests exceeds throttled limits"); + } else if (exception instanceof FirebaseRemoteConfigClientException) { + details.put("code", "internal"); + details.put("message", "internal remote config fetch error"); + } else { + details.put("code", "unknown"); + details.put("message", "unknown remote config error"); + } + result.error(null, exception != null ? exception.getMessage() : null, details); + } + }); + } + + private Map parseParameters(Map parameters) { + Map parsedParameters = new HashMap<>(); + for (String key : parameters.keySet()) { + parsedParameters.put(key, createRemoteConfigValueMap(parameters.get(key))); + } + return parsedParameters; + } + + private Map createRemoteConfigValueMap( + FirebaseRemoteConfigValue remoteConfigValue) { + Map valueMap = new HashMap<>(); + valueMap.put("value", remoteConfigValue.asByteArray()); + valueMap.put("source", mapValueSource(remoteConfigValue.getSource())); + return valueMap; + } + + private String mapLastFetchStatus(int status) { + switch (status) { + case FirebaseRemoteConfig.LAST_FETCH_STATUS_SUCCESS: + return "success"; + case FirebaseRemoteConfig.LAST_FETCH_STATUS_FAILURE: + return "failure"; + case FirebaseRemoteConfig.LAST_FETCH_STATUS_THROTTLED: + return "throttled"; + case FirebaseRemoteConfig.LAST_FETCH_STATUS_NO_FETCH_YET: + return "noFetchYet"; + default: + return "failure"; + } + } + + private String mapValueSource(int source) { + switch (source) { + case FirebaseRemoteConfig.VALUE_SOURCE_STATIC: + return "static"; + case FirebaseRemoteConfig.VALUE_SOURCE_DEFAULT: + return "default"; + case FirebaseRemoteConfig.VALUE_SOURCE_REMOTE: + return "remote"; + default: + return "static"; + } + } +} diff --git a/packages/firebase_remote_config/android/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfig/FlutterFirebaseAppRegistrar.java b/packages/firebase_remote_config/firebase_remote_config/android/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfig/FlutterFirebaseAppRegistrar.java similarity index 100% rename from packages/firebase_remote_config/android/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfig/FlutterFirebaseAppRegistrar.java rename to packages/firebase_remote_config/firebase_remote_config/android/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfig/FlutterFirebaseAppRegistrar.java diff --git a/packages/firebase_remote_config/android/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfig/MethodCallHandlerImpl.java b/packages/firebase_remote_config/firebase_remote_config/android/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfig/MethodCallHandlerImpl.java similarity index 100% rename from packages/firebase_remote_config/android/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfig/MethodCallHandlerImpl.java rename to packages/firebase_remote_config/firebase_remote_config/android/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfig/MethodCallHandlerImpl.java diff --git a/packages/firebase_remote_config/android/user-agent.gradle b/packages/firebase_remote_config/firebase_remote_config/android/user-agent.gradle similarity index 100% rename from packages/firebase_remote_config/android/user-agent.gradle rename to packages/firebase_remote_config/firebase_remote_config/android/user-agent.gradle diff --git a/packages/firebase_remote_config/example/.metadata b/packages/firebase_remote_config/firebase_remote_config/example/.metadata similarity index 100% rename from packages/firebase_remote_config/example/.metadata rename to packages/firebase_remote_config/firebase_remote_config/example/.metadata diff --git a/packages/firebase_remote_config/example/README.md b/packages/firebase_remote_config/firebase_remote_config/example/README.md similarity index 100% rename from packages/firebase_remote_config/example/README.md rename to packages/firebase_remote_config/firebase_remote_config/example/README.md diff --git a/packages/firebase_remote_config/firebase_remote_config/example/analysis_options.yaml b/packages/firebase_remote_config/firebase_remote_config/example/analysis_options.yaml new file mode 100644 index 000000000000..7b6ffe43e7c3 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config/example/analysis_options.yaml @@ -0,0 +1,3 @@ +analyzer: + errors: + public_member_api_docs: ignore diff --git a/packages/firebase_remote_config/example/android/app/build.gradle b/packages/firebase_remote_config/firebase_remote_config/example/android/app/build.gradle similarity index 90% rename from packages/firebase_remote_config/example/android/app/build.gradle rename to packages/firebase_remote_config/firebase_remote_config/example/android/app/build.gradle index 96b4fdc60f7a..6423ad5e21e5 100644 --- a/packages/firebase_remote_config/example/android/app/build.gradle +++ b/packages/firebase_remote_config/firebase_remote_config/example/android/app/build.gradle @@ -26,6 +26,10 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 29 + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } lintOptions { disable 'InvalidPackage' @@ -59,4 +63,4 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } -apply plugin: 'com.google.gms.google-services' \ No newline at end of file +apply plugin: 'com.google.gms.google-services' diff --git a/packages/firebase_remote_config/example/android/app/google-services.json b/packages/firebase_remote_config/firebase_remote_config/example/android/app/google-services.json similarity index 100% rename from packages/firebase_remote_config/example/android/app/google-services.json rename to packages/firebase_remote_config/firebase_remote_config/example/android/app/google-services.json diff --git a/packages/firebase_remote_config/example/android/app/gradle/wrapper/gradle-wrapper.properties b/packages/firebase_remote_config/firebase_remote_config/example/android/app/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from packages/firebase_remote_config/example/android/app/gradle/wrapper/gradle-wrapper.properties rename to packages/firebase_remote_config/firebase_remote_config/example/android/app/gradle/wrapper/gradle-wrapper.properties diff --git a/packages/firebase_remote_config/example/android/app/src/main/AndroidManifest.xml b/packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/AndroidManifest.xml similarity index 100% rename from packages/firebase_remote_config/example/android/app/src/main/AndroidManifest.xml rename to packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/AndroidManifest.xml diff --git a/packages/firebase_remote_config/example/android/app/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfigexample/EmbeddingV1Activity.java b/packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfigexample/EmbeddingV1Activity.java similarity index 100% rename from packages/firebase_remote_config/example/android/app/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfigexample/EmbeddingV1Activity.java rename to packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfigexample/EmbeddingV1Activity.java diff --git a/packages/firebase_remote_config/example/android/app/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfigexample/EmbeddingV1ActivityTest.java b/packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfigexample/EmbeddingV1ActivityTest.java similarity index 100% rename from packages/firebase_remote_config/example/android/app/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfigexample/EmbeddingV1ActivityTest.java rename to packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/java/io/flutter/plugins/firebase/firebaseremoteconfigexample/EmbeddingV1ActivityTest.java diff --git a/packages/firebase_remote_config/example/android/app/src/main/res/drawable/launch_background.xml b/packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from packages/firebase_remote_config/example/android/app/src/main/res/drawable/launch_background.xml rename to packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/res/drawable/launch_background.xml diff --git a/packages/firebase_remote_config/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from packages/firebase_remote_config/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/packages/firebase_remote_config/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from packages/firebase_remote_config/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/packages/firebase_remote_config/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from packages/firebase_remote_config/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/packages/firebase_remote_config/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from packages/firebase_remote_config/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/packages/firebase_remote_config/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from packages/firebase_remote_config/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/packages/firebase_remote_config/example/android/app/src/main/res/values/styles.xml b/packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/res/values/styles.xml similarity index 100% rename from packages/firebase_remote_config/example/android/app/src/main/res/values/styles.xml rename to packages/firebase_remote_config/firebase_remote_config/example/android/app/src/main/res/values/styles.xml diff --git a/packages/firebase_remote_config/example/android/build.gradle b/packages/firebase_remote_config/firebase_remote_config/example/android/build.gradle similarity index 100% rename from packages/firebase_remote_config/example/android/build.gradle rename to packages/firebase_remote_config/firebase_remote_config/example/android/build.gradle diff --git a/packages/firebase_remote_config/example/android/gradle.properties b/packages/firebase_remote_config/firebase_remote_config/example/android/gradle.properties similarity index 100% rename from packages/firebase_remote_config/example/android/gradle.properties rename to packages/firebase_remote_config/firebase_remote_config/example/android/gradle.properties diff --git a/packages/firebase_remote_config/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/firebase_remote_config/firebase_remote_config/example/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from packages/firebase_remote_config/example/android/gradle/wrapper/gradle-wrapper.properties rename to packages/firebase_remote_config/firebase_remote_config/example/android/gradle/wrapper/gradle-wrapper.properties diff --git a/packages/firebase_remote_config/example/android/settings.gradle b/packages/firebase_remote_config/firebase_remote_config/example/android/settings.gradle similarity index 100% rename from packages/firebase_remote_config/example/android/settings.gradle rename to packages/firebase_remote_config/firebase_remote_config/example/android/settings.gradle diff --git a/packages/firebase_remote_config/example/ios/Flutter/AppFrameworkInfo.plist b/packages/firebase_remote_config/firebase_remote_config/example/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from packages/firebase_remote_config/example/ios/Flutter/AppFrameworkInfo.plist rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Flutter/AppFrameworkInfo.plist diff --git a/packages/firebase_remote_config/example/ios/Flutter/Debug.xcconfig b/packages/firebase_remote_config/firebase_remote_config/example/ios/Flutter/Debug.xcconfig similarity index 100% rename from packages/firebase_remote_config/example/ios/Flutter/Debug.xcconfig rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Flutter/Debug.xcconfig diff --git a/packages/firebase_remote_config/example/ios/Flutter/Release.xcconfig b/packages/firebase_remote_config/firebase_remote_config/example/ios/Flutter/Release.xcconfig similarity index 100% rename from packages/firebase_remote_config/example/ios/Flutter/Release.xcconfig rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Flutter/Release.xcconfig diff --git a/packages/firebase_remote_config/example/ios/Runner.xcodeproj/project.pbxproj b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner.xcodeproj/project.pbxproj similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner.xcodeproj/project.pbxproj rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner.xcodeproj/project.pbxproj diff --git a/packages/firebase_remote_config/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/packages/firebase_remote_config/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/packages/firebase_remote_config/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner.xcworkspace/contents.xcworkspacedata rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/packages/firebase_remote_config/example/ios/Runner/AppDelegate.h b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/AppDelegate.h similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/AppDelegate.h rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/AppDelegate.h diff --git a/packages/firebase_remote_config/example/ios/Runner/AppDelegate.m b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/AppDelegate.m similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/AppDelegate.m rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/AppDelegate.m diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/packages/firebase_remote_config/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/packages/firebase_remote_config/example/ios/Runner/Base.lproj/Main.storyboard b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Base.lproj/Main.storyboard rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Base.lproj/Main.storyboard diff --git a/packages/firebase_remote_config/example/ios/Runner/GoogleService-Info.plist b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/GoogleService-Info.plist similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/GoogleService-Info.plist rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/GoogleService-Info.plist diff --git a/packages/firebase_remote_config/example/ios/Runner/Info.plist b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Info.plist similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/Info.plist rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/Info.plist diff --git a/packages/firebase_remote_config/example/ios/Runner/main.m b/packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/main.m similarity index 100% rename from packages/firebase_remote_config/example/ios/Runner/main.m rename to packages/firebase_remote_config/firebase_remote_config/example/ios/Runner/main.m diff --git a/packages/firebase_remote_config/example/lib/main.dart b/packages/firebase_remote_config/firebase_remote_config/example/lib/main.dart similarity index 58% rename from packages/firebase_remote_config/example/lib/main.dart rename to packages/firebase_remote_config/firebase_remote_config/example/lib/main.dart index 1e8e420050b4..6cc7fe09ccda 100644 --- a/packages/firebase_remote_config/example/lib/main.dart +++ b/packages/firebase_remote_config/firebase_remote_config/example/lib/main.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_remote_config/firebase_remote_config.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); @@ -33,21 +34,38 @@ class WelcomeWidget extends AnimatedWidget { appBar: AppBar( title: const Text('Remote Config Example'), ), - body: Center(child: Text('Welcome ${remoteConfig.getString('welcome')}')), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Welcome ${remoteConfig.getString('welcome')}'), + SizedBox( + height: 20, + ), + Text('(${remoteConfig.getValue('welcome').source})'), + Text('(${remoteConfig.lastFetchTime})'), + Text('(${remoteConfig.lastFetchStatus})'), + ], + ), + ), floatingActionButton: FloatingActionButton( child: const Icon(Icons.refresh), onPressed: () async { try { - // Using default duration to force fetching from remote server. - await remoteConfig.fetch(expiration: const Duration(seconds: 0)); - await remoteConfig.activateFetched(); - } on FetchThrottledException catch (exception) { - // Fetch throttled. + // Using zero duration to force fetching from remote server. + await remoteConfig.setConfigSettings(RemoteConfigSettings( + fetchTimeout: Duration(seconds: 10), + minimumFetchInterval: Duration.zero, + )); + await remoteConfig.fetchAndActivate(); + } on PlatformException catch (exception) { + // Fetch exception. print(exception); } catch (exception) { print( 'Unable to fetch remote config. Cached or default values will be ' 'used'); + print(exception); } }), ); @@ -57,12 +75,14 @@ class WelcomeWidget extends AnimatedWidget { Future setupRemoteConfig() async { await Firebase.initializeApp(); final RemoteConfig remoteConfig = await RemoteConfig.instance; - // Allow a fetch every millisecond. Default is 12 hours. - remoteConfig - .setConfigSettings(RemoteConfigSettings(minimumFetchIntervalMillis: 1)); - remoteConfig.setDefaults({ + await remoteConfig.setConfigSettings(RemoteConfigSettings( + fetchTimeout: Duration(seconds: 10), + minimumFetchInterval: Duration(hours: 1), + )); + await remoteConfig.setDefaults({ 'welcome': 'default welcome', 'hello': 'default hello', }); + RemoteConfigValue(null, ValueSource.valueStatic); return remoteConfig; } diff --git a/packages/firebase_remote_config/example/macos/.gitignore b/packages/firebase_remote_config/firebase_remote_config/example/macos/.gitignore similarity index 100% rename from packages/firebase_remote_config/example/macos/.gitignore rename to packages/firebase_remote_config/firebase_remote_config/example/macos/.gitignore diff --git a/packages/firebase_remote_config/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/firebase_remote_config/firebase_remote_config/example/macos/Flutter/Flutter-Debug.xcconfig similarity index 100% rename from packages/firebase_remote_config/example/macos/Flutter/Flutter-Debug.xcconfig rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Flutter/Flutter-Debug.xcconfig diff --git a/packages/firebase_remote_config/example/macos/Flutter/Flutter-Release.xcconfig b/packages/firebase_remote_config/firebase_remote_config/example/macos/Flutter/Flutter-Release.xcconfig similarity index 100% rename from packages/firebase_remote_config/example/macos/Flutter/Flutter-Release.xcconfig rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Flutter/Flutter-Release.xcconfig diff --git a/packages/firebase_remote_config/example/macos/Podfile b/packages/firebase_remote_config/firebase_remote_config/example/macos/Podfile similarity index 100% rename from packages/firebase_remote_config/example/macos/Podfile rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Podfile diff --git a/packages/firebase_remote_config/example/macos/Runner.xcodeproj/project.pbxproj b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner.xcodeproj/project.pbxproj similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner.xcodeproj/project.pbxproj rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner.xcodeproj/project.pbxproj diff --git a/packages/firebase_remote_config/example/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/packages/firebase_remote_config/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/firebase_remote_config/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/packages/firebase_remote_config/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner.xcworkspace/contents.xcworkspacedata rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner.xcworkspace/contents.xcworkspacedata diff --git a/packages/firebase_remote_config/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/firebase_remote_config/example/macos/Runner/AppDelegate.swift b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/AppDelegate.swift similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner/AppDelegate.swift rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/AppDelegate.swift diff --git a/packages/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/packages/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png diff --git a/packages/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png diff --git a/packages/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png diff --git a/packages/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png diff --git a/packages/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png diff --git a/packages/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png diff --git a/packages/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png diff --git a/packages/firebase_remote_config/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Base.lproj/MainMenu.xib similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner/Base.lproj/MainMenu.xib rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Base.lproj/MainMenu.xib diff --git a/packages/firebase_remote_config/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Configs/AppInfo.xcconfig similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner/Configs/AppInfo.xcconfig rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Configs/AppInfo.xcconfig diff --git a/packages/firebase_remote_config/example/macos/Runner/Configs/Debug.xcconfig b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Configs/Debug.xcconfig similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner/Configs/Debug.xcconfig rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Configs/Debug.xcconfig diff --git a/packages/firebase_remote_config/example/macos/Runner/Configs/Release.xcconfig b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Configs/Release.xcconfig similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner/Configs/Release.xcconfig rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Configs/Release.xcconfig diff --git a/packages/firebase_remote_config/example/macos/Runner/Configs/Warnings.xcconfig b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Configs/Warnings.xcconfig similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner/Configs/Warnings.xcconfig rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Configs/Warnings.xcconfig diff --git a/packages/firebase_remote_config/example/macos/Runner/DebugProfile.entitlements b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/DebugProfile.entitlements similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner/DebugProfile.entitlements rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/DebugProfile.entitlements diff --git a/packages/firebase_remote_config/example/macos/Runner/GoogleService-Info.plist b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/GoogleService-Info.plist similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner/GoogleService-Info.plist rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/GoogleService-Info.plist diff --git a/packages/firebase_remote_config/example/macos/Runner/Info.plist b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Info.plist similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner/Info.plist rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Info.plist diff --git a/packages/firebase_remote_config/example/macos/Runner/MainFlutterWindow.swift b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/MainFlutterWindow.swift similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner/MainFlutterWindow.swift rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/MainFlutterWindow.swift diff --git a/packages/firebase_remote_config/example/macos/Runner/Release.entitlements b/packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Release.entitlements similarity index 100% rename from packages/firebase_remote_config/example/macos/Runner/Release.entitlements rename to packages/firebase_remote_config/firebase_remote_config/example/macos/Runner/Release.entitlements diff --git a/packages/firebase_remote_config/example/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config/example/pubspec.yaml similarity index 72% rename from packages/firebase_remote_config/example/pubspec.yaml rename to packages/firebase_remote_config/firebase_remote_config/example/pubspec.yaml index 8bbbc82cfc1a..12f65e88b352 100644 --- a/packages/firebase_remote_config/example/pubspec.yaml +++ b/packages/firebase_remote_config/firebase_remote_config/example/pubspec.yaml @@ -1,6 +1,9 @@ name: firebase_remote_config_example description: Demonstrates how to use the firebase_remote_config plugin. +flutter: + uses-material-design: true + dependencies: flutter: sdk: flutter @@ -10,11 +13,13 @@ dependencies: firebase_remote_config: path: ../ firebase_core: - path: ../../firebase_core/firebase_core + path: ../../../firebase_core/firebase_core dependency_overrides: firebase_core: - path: ../../firebase_core/firebase_core + path: ../../../firebase_core/firebase_core + firebase_remote_config_platform_interface: + path: ../../firebase_remote_config_platform_interface dev_dependencies: pedantic: ^1.8.0 diff --git a/packages/firebase_remote_config/example/test_driver/firebase_remote_config_e2e.dart b/packages/firebase_remote_config/firebase_remote_config/example/test_driver/firebase_remote_config_e2e.dart similarity index 50% rename from packages/firebase_remote_config/example/test_driver/firebase_remote_config_e2e.dart rename to packages/firebase_remote_config/firebase_remote_config/example/test_driver/firebase_remote_config_e2e.dart index 7d2c6386ada0..bb30a3e45da5 100644 --- a/packages/firebase_remote_config/example/test_driver/firebase_remote_config_e2e.dart +++ b/packages/firebase_remote_config/firebase_remote_config/example/test_driver/firebase_remote_config_e2e.dart @@ -12,9 +12,10 @@ void main() { setUp(() async { await Firebase.initializeApp(); remoteConfig = await RemoteConfig.instance; - // Set our config to no minimum fetch interval so that settings change immediately await remoteConfig.setConfigSettings(RemoteConfigSettings( - minimumFetchIntervalMillis: 0, fetchTimeoutMillis: 30 * 1000)); + fetchTimeout: Duration(seconds: 8), + minimumFetchInterval: Duration.zero, + )); await remoteConfig.setDefaults({ 'welcome': 'default welcome', 'hello': 'default hello', @@ -22,12 +23,11 @@ void main() { }); testWidgets('fetch', (WidgetTester tester) async { - final DateTime lastFetchTime = remoteConfig.lastFetchTime; - expect(lastFetchTime.isBefore(DateTime.now()), true); - await remoteConfig.fetch(expiration: const Duration(seconds: 0)); - expect(remoteConfig.lastFetchStatus, LastFetchStatus.success); - final activated = await remoteConfig.activateFetched(); - expect(activated, true); + final mark = DateTime.now(); + expect(remoteConfig.lastFetchTime.isBefore(mark), true); + await remoteConfig.fetchAndActivate(); + expect(remoteConfig.lastFetchStatus, RemoteConfigFetchStatus.success); + expect(remoteConfig.lastFetchTime.isAfter(mark), true); // TODO should verify that our config settings actually took expect(remoteConfig.getString('welcome'), 'Earth, welcome! Hello!'); @@ -43,5 +43,21 @@ void main() { ValueSource.valueStatic, ); }); + + testWidgets('settings', (WidgetTester tester) async { + expect(remoteConfig.settings.fetchTimeout, Duration(seconds: 8)); + expect(remoteConfig.settings.minimumFetchInterval, Duration.zero); + await remoteConfig.setConfigSettings(RemoteConfigSettings( + fetchTimeout: Duration.zero, + minimumFetchInterval: Duration(seconds: 88), + )); + expect(remoteConfig.settings.fetchTimeout, Duration(seconds: 60)); + expect(remoteConfig.settings.minimumFetchInterval, Duration(seconds: 88)); + await remoteConfig.setConfigSettings(RemoteConfigSettings( + fetchTimeout: Duration(seconds: 10, milliseconds: 500), + minimumFetchInterval: Duration.zero)); + expect(remoteConfig.settings.fetchTimeout, Duration(seconds: 10)); + expect(remoteConfig.settings.minimumFetchInterval, Duration.zero); + }); }); } diff --git a/packages/firebase_remote_config/example/test_driver/firebase_remote_config_e2e_test.dart b/packages/firebase_remote_config/firebase_remote_config/example/test_driver/firebase_remote_config_e2e_test.dart similarity index 100% rename from packages/firebase_remote_config/example/test_driver/firebase_remote_config_e2e_test.dart rename to packages/firebase_remote_config/firebase_remote_config/example/test_driver/firebase_remote_config_e2e_test.dart diff --git a/packages/firebase_remote_config/ios/Assets/.gitkeep b/packages/firebase_remote_config/firebase_remote_config/ios/Assets/.gitkeep similarity index 100% rename from packages/firebase_remote_config/ios/Assets/.gitkeep rename to packages/firebase_remote_config/firebase_remote_config/ios/Assets/.gitkeep diff --git a/packages/firebase_remote_config/ios/Classes/FirebaseRemoteConfigPlugin.h b/packages/firebase_remote_config/firebase_remote_config/ios/Classes/FLTFirebaseRemoteConfigPlugin.h similarity index 60% rename from packages/firebase_remote_config/ios/Classes/FirebaseRemoteConfigPlugin.h rename to packages/firebase_remote_config/firebase_remote_config/ios/Classes/FLTFirebaseRemoteConfigPlugin.h index bfce9d9a7c9b..78281de9d1fd 100644 --- a/packages/firebase_remote_config/ios/Classes/FirebaseRemoteConfigPlugin.h +++ b/packages/firebase_remote_config/firebase_remote_config/ios/Classes/FLTFirebaseRemoteConfigPlugin.h @@ -8,5 +8,8 @@ #import #endif -@interface FirebaseRemoteConfigPlugin : NSObject +#import +#import + +@interface FLTFirebaseRemoteConfigPlugin : FLTFirebasePlugin @end diff --git a/packages/firebase_remote_config/firebase_remote_config/ios/Classes/FLTFirebaseRemoteConfigPlugin.m b/packages/firebase_remote_config/firebase_remote_config/ios/Classes/FLTFirebaseRemoteConfigPlugin.m new file mode 100644 index 000000000000..7398e9fbf490 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config/ios/Classes/FLTFirebaseRemoteConfigPlugin.m @@ -0,0 +1,266 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import + +#import "FLTFirebaseRemoteConfigPlugin.h" +#import "FLTFirebaseRemoteConfigUtils.h" + +NSString *const kFirebaseRemoteConfigChannelName = @"plugins.flutter.io/firebase_remote_config"; + +@interface FLTFirebaseRemoteConfigPlugin () +@property(nonatomic, retain) FlutterMethodChannel *channel; +@end + +@implementation FLTFirebaseRemoteConfigPlugin + ++ (instancetype)sharedInstance { + static dispatch_once_t onceToken; + static FLTFirebaseRemoteConfigPlugin *instance; + + dispatch_once(&onceToken, ^{ + instance = [[FLTFirebaseRemoteConfigPlugin alloc] init]; + [[FLTFirebasePluginRegistry sharedInstance] registerFirebasePlugin:instance]; + }); + + return instance; +} + +- (instancetype)init { + self = [super init]; + return self; +} + ++ (void)registerWithRegistrar:(NSObject *)registrar { + FlutterMethodChannel *channel = + [FlutterMethodChannel methodChannelWithName:kFirebaseRemoteConfigChannelName + binaryMessenger:[registrar messenger]]; + FLTFirebaseRemoteConfigPlugin *instance = [FLTFirebaseRemoteConfigPlugin sharedInstance]; + + [registrar addMethodCallDelegate:instance channel:channel]; + + SEL sel = NSSelectorFromString(@"registerLibrary:withVersion:"); + if ([FIRApp respondsToSelector:sel]) { + [FIRApp performSelector:sel withObject:LIBRARY_NAME withObject:LIBRARY_VERSION]; + } +} + +- (void)detachFromEngineForRegistrar:(NSObject *)registrar { + self.channel = nil; +} + +- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)flutterResult { + FLTFirebaseMethodCallErrorBlock errorBlock = + ^(NSString *_Nullable code, NSString *_Nullable message, NSDictionary *_Nullable details, + NSError *_Nullable error) { + if (code == nil) { + details = [FLTFirebaseRemoteConfigUtils ErrorCodeAndMessageFromNSError:error]; + code = [details valueForKey:@"code"]; + message = [details valueForKey:@"message"]; + } + if ([@"unknown" isEqualToString:code]) { + NSLog(@"FLTFirebaseRemoteConfig: An error occurred while calling method %@", call.method); + } + flutterResult([FLTFirebasePlugin createFlutterErrorFromCode:code + message:message + optionalDetails:details + andOptionalNSError:error]); + }; + + FLTFirebaseMethodCallResult *methodCallResult = + [FLTFirebaseMethodCallResult createWithSuccess:flutterResult andErrorBlock:errorBlock]; + + if ([@"RemoteConfig#ensureInitialized" isEqualToString:call.method]) { + [self ensureInitialized:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"RemoteConfig#activate" isEqualToString:call.method]) { + [self activate:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"RemoteConfig#getAll" isEqualToString:call.method]) { + [self getAll:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"RemoteConfig#fetch" isEqualToString:call.method]) { + [self fetch:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"RemoteConfig#fetchAndActivate" isEqualToString:call.method]) { + [self fetchAndActivate:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"RemoteConfig#setConfigSettings" isEqualToString:call.method]) { + [self setConfigSettings:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"RemoteConfig#setDefaults" isEqualToString:call.method]) { + [self setDefaults:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"RemoteConfig#getProperties" isEqualToString:call.method]) { + [self getProperties:call.arguments withMethodCallResult:methodCallResult]; + } else { + methodCallResult.success(FlutterMethodNotImplemented); + } +} + +#pragma mark - Remote Config API +- (void)ensureInitialized:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + FIRRemoteConfig *remoteConfig = [self getFIRRemoteConfigFromArguments:arguments]; + [remoteConfig ensureInitializedWithCompletionHandler:^(NSError *initializationError) { + if (initializationError != nil) { + result.error(nil, nil, nil, initializationError); + } else { + result.success(nil); + } + }]; +} + +- (void)activate:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + FIRRemoteConfig *remoteConfig = [self getFIRRemoteConfigFromArguments:arguments]; + [remoteConfig activateWithCompletion:^(BOOL changed, NSError *error) { + if (error != nil) { + result.error(nil, nil, nil, error); + } else { + result.success(@(changed)); + } + }]; +} + +- (void)getAll:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + FIRRemoteConfig *remoteConfig = [self getFIRRemoteConfigFromArguments:arguments]; + NSDictionary *parameters = [self getAllParametersForInstance:remoteConfig]; + result.success(parameters); +} + +- (void)fetch:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + FIRRemoteConfig *remoteConfig = [self getFIRRemoteConfigFromArguments:arguments]; + [remoteConfig fetchWithCompletionHandler:^(FIRRemoteConfigFetchStatus status, NSError *error) { + if (error != nil) { + result.error(nil, nil, nil, error); + } else { + result.success(nil); + } + }]; +} + +- (void)getProperties:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + FIRRemoteConfig *remoteConfig = [self getFIRRemoteConfigFromArguments:arguments]; + NSDictionary *configProperties = [self configPropertiesForInstance:remoteConfig]; + result.success(configProperties); +} + +- (void)setDefaults:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + FIRRemoteConfig *remoteConfig = [self getFIRRemoteConfigFromArguments:arguments]; + [remoteConfig setDefaults:arguments[@"defaults"]]; + result.success(nil); +} + +- (void)setConfigSettings:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + NSNumber *fetchTimeout = arguments[@"fetchTimeout"]; + NSNumber *minimumFetchInterval = arguments[@"minimumFetchInterval"]; + FIRRemoteConfigSettings *remoteConfigSettings = [[FIRRemoteConfigSettings alloc] init]; + remoteConfigSettings.fetchTimeout = [fetchTimeout doubleValue]; + remoteConfigSettings.minimumFetchInterval = [minimumFetchInterval doubleValue]; + FIRRemoteConfig *remoteConfig = [self getFIRRemoteConfigFromArguments:arguments]; + [remoteConfig setConfigSettings:remoteConfigSettings]; + result.success(nil); +} + +- (void)fetchAndActivate:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + FIRRemoteConfig *remoteConfig = [self getFIRRemoteConfigFromArguments:arguments]; + [remoteConfig fetchAndActivateWithCompletionHandler:^( + FIRRemoteConfigFetchAndActivateStatus status, NSError *error) { + if (error != nil) { + result.error(nil, nil, nil, error); + } else { + result.success(nil); + } + }]; +} + +- (FIRRemoteConfig *_Nullable)getFIRRemoteConfigFromArguments:(NSDictionary *)arguments { + NSString *appName = arguments[@"appName"]; + FIRApp *app = [FLTFirebasePlugin firebaseAppNamed:appName]; + return [FIRRemoteConfig remoteConfigWithApp:app]; +} + +- (NSDictionary *)getAllParametersForInstance:(FIRRemoteConfig *)remoteConfig { + NSMutableSet *keySet = [[NSMutableSet alloc] init]; + [keySet addObjectsFromArray:[remoteConfig allKeysFromSource:FIRRemoteConfigSourceStatic]]; + [keySet addObjectsFromArray:[remoteConfig allKeysFromSource:FIRRemoteConfigSourceDefault]]; + [keySet addObjectsFromArray:[remoteConfig allKeysFromSource:FIRRemoteConfigSourceRemote]]; + + NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init]; + for (NSString *key in keySet) { + parameters[key] = [self createRemoteConfigValueDict:[remoteConfig configValueForKey:key]]; + } + return parameters; +} + +- (NSMutableDictionary *)createRemoteConfigValueDict:(FIRRemoteConfigValue *)remoteConfigValue { + NSMutableDictionary *valueDict = [[NSMutableDictionary alloc] init]; + valueDict[@"value"] = [FlutterStandardTypedData typedDataWithBytes:[remoteConfigValue dataValue]]; + valueDict[@"source"] = [self mapValueSource:[remoteConfigValue source]]; + return valueDict; +} + +- (NSString *)mapLastFetchStatus:(FIRRemoteConfigFetchStatus)status { + if (status == FIRRemoteConfigFetchStatusSuccess) { + return @"success"; + } else if (status == FIRRemoteConfigFetchStatusFailure) { + return @"failure"; + } else if (status == FIRRemoteConfigFetchStatusThrottled) { + return @"throttled"; + } else if (status == FIRRemoteConfigFetchStatusNoFetchYet) { + return @"noFetchYet"; + } else { + return @"failure"; + } +} + +- (NSString *)mapValueSource:(FIRRemoteConfigSource)source { + if (source == FIRRemoteConfigSourceStatic) { + return @"static"; + } else if (source == FIRRemoteConfigSourceDefault) { + return @"default"; + } else if (source == FIRRemoteConfigSourceRemote) { + return @"remote"; + } else { + return @"static"; + } +} + +#pragma mark - FLTFirebasePlugin + +- (void)didReinitializeFirebaseCore:(void (^)(void))completion { + completion(); +} + +- (NSDictionary *_Nonnull)pluginConstantsForFIRApp:(FIRApp *)firebase_app { + FIRRemoteConfig *firebaseRemoteConfig = [FIRRemoteConfig remoteConfigWithApp:firebase_app]; + NSDictionary *configProperties = [self configPropertiesForInstance:firebaseRemoteConfig]; + + NSMutableDictionary *configValues = [[NSMutableDictionary alloc] init]; + [configValues addEntriesFromDictionary:configProperties]; + [configValues setValue:[self getAllParametersForInstance:firebaseRemoteConfig] + forKey:@"parameters"]; + return configValues; +} + +- (NSDictionary *_Nonnull)configPropertiesForInstance:(FIRRemoteConfig *)remoteConfig { + NSNumber *fetchTimeout = @([[remoteConfig configSettings] fetchTimeout]); + NSNumber *minimumFetchInterval = @([[remoteConfig configSettings] minimumFetchInterval]); + NSNumber *lastFetchMillis = @([[remoteConfig lastFetchTime] timeIntervalSince1970] * 1000); + + NSMutableDictionary *configProperties = [[NSMutableDictionary alloc] init]; + [configProperties setValue:@([fetchTimeout longValue]) forKey:@"fetchTimeout"]; + [configProperties setValue:@([minimumFetchInterval longValue]) forKey:@"minimumFetchInterval"]; + [configProperties setValue:@([lastFetchMillis longValue]) forKey:@"lastFetchTime"]; + [configProperties setValue:[self mapLastFetchStatus:[remoteConfig lastFetchStatus]] + forKey:@"lastFetchStatus"]; + return configProperties; +} + +- (NSString *_Nonnull)firebaseLibraryName { + return LIBRARY_NAME; +} + +- (NSString *_Nonnull)firebaseLibraryVersion { + return LIBRARY_VERSION; +} + +- (NSString *_Nonnull)flutterChannelName { + return kFirebaseRemoteConfigChannelName; +} + +@end diff --git a/packages/firebase_remote_config/firebase_remote_config/ios/Classes/FLTFirebaseRemoteConfigUtils.h b/packages/firebase_remote_config/firebase_remote_config/ios/Classes/FLTFirebaseRemoteConfigUtils.h new file mode 100644 index 000000000000..f6f655a08c61 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config/ios/Classes/FLTFirebaseRemoteConfigUtils.h @@ -0,0 +1,9 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import + +@interface FLTFirebaseRemoteConfigUtils : NSObject ++ (NSDictionary *)ErrorCodeAndMessageFromNSError:(NSError *)error; +@end diff --git a/packages/firebase_remote_config/firebase_remote_config/ios/Classes/FLTFirebaseRemoteConfigUtils.m b/packages/firebase_remote_config/firebase_remote_config/ios/Classes/FLTFirebaseRemoteConfigUtils.m new file mode 100644 index 000000000000..a3aff6835a38 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config/ios/Classes/FLTFirebaseRemoteConfigUtils.m @@ -0,0 +1,28 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import + +#import "FLTFirebaseRemoteConfigUtils.h" + +@implementation FLTFirebaseRemoteConfigUtils ++ (NSDictionary *)ErrorCodeAndMessageFromNSError:(NSError *)error { + NSMutableDictionary *codeAndMessage = [[NSMutableDictionary alloc] init]; + switch (error.code) { + case FIRRemoteConfigErrorInternalError: + [codeAndMessage setValue:@"internal" forKey:@"code"]; + [codeAndMessage setValue:@"internal remote config fetch error" forKey:@"message"]; + break; + case FIRRemoteConfigErrorThrottled: + [codeAndMessage setValue:@"throttled" forKey:@"code"]; + [codeAndMessage setValue:@"frequency of requests exceeds throttled limits" forKey:@"message"]; + break; + default: + [codeAndMessage setValue:@"unknown" forKey:@"code"]; + [codeAndMessage setValue:@"unknown remote config error" forKey:@"message"]; + break; + } + return codeAndMessage; +} +@end diff --git a/packages/firebase_remote_config/ios/firebase_remote_config.podspec b/packages/firebase_remote_config/firebase_remote_config/ios/firebase_remote_config.podspec similarity index 100% rename from packages/firebase_remote_config/ios/firebase_remote_config.podspec rename to packages/firebase_remote_config/firebase_remote_config/ios/firebase_remote_config.podspec diff --git a/packages/firebase_remote_config/firebase_remote_config/lib/firebase_remote_config.dart b/packages/firebase_remote_config/firebase_remote_config/lib/firebase_remote_config.dart new file mode 100644 index 000000000000..34ad0b54cafb --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config/lib/firebase_remote_config.dart @@ -0,0 +1,22 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +library firebase_remote_config; + +import 'dart:async'; + +import 'package:firebase_remote_config_platform_interface/firebase_remote_config_platform_interface.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart' + show FirebasePluginPlatform; +import 'package:flutter/foundation.dart'; + +export 'package:firebase_remote_config_platform_interface/firebase_remote_config_platform_interface.dart' + show + RemoteConfigSettings, + ValueSource, + RemoteConfigFetchStatus, + RemoteConfigValue; + +part 'src/remote_config.dart'; diff --git a/packages/firebase_remote_config/firebase_remote_config/lib/src/remote_config.dart b/packages/firebase_remote_config/firebase_remote_config/lib/src/remote_config.dart new file mode 100644 index 000000000000..bbf9ec4c1cbc --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config/lib/src/remote_config.dart @@ -0,0 +1,161 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of firebase_remote_config; + +/// The entry point for accessing Remote Config. +/// +/// You can get an instance by calling [RemoteConfig.instance]. Note +/// [RemoteConfig.instance] is async. +class RemoteConfig extends FirebasePluginPlatform with ChangeNotifier { + // Cached instances of [FirebaseRemoteConfig]. + static final Map _firebaseRemoteConfigInstances = {}; + + // Cached and lazily loaded instance of [FirebaseRemoteConfigPlatform] + // to avoid creating a [MethodChannelFirebaseRemoteConfig] when not needed + // or creating an instance with the default app before a user specifies an + // app. + FirebaseRemoteConfigPlatform _delegatePackingProperty; + + /// Returns the underlying delegate implementation. + /// + /// If called and no [_delegatePackingProperty] exists, it will first be + /// created and assigned before returning the delegate. + FirebaseRemoteConfigPlatform get _delegate { + _delegatePackingProperty ??= FirebaseRemoteConfigPlatform.instanceFor( + app: app, + pluginConstants: pluginConstants, + ); + return _delegatePackingProperty; + } + + /// The [FirebaseApp] this instance was initialized with. + final FirebaseApp app; + + RemoteConfig._({this.app}) + : super(app.name, 'plugins.flutter.io/firebase_remote_config'); + + /// Returns an instance using the default [FirebaseApp]. + static RemoteConfig get instance { + return RemoteConfig.instanceFor(app: Firebase.app()); + } + + /// Returns an instance using the specified [FirebaseApp]. + static RemoteConfig instanceFor({@required FirebaseApp app}) { + assert(app != null); + + if (_firebaseRemoteConfigInstances.containsKey(app.name)) { + return _firebaseRemoteConfigInstances[app.name]; + } + + RemoteConfig newInstance = RemoteConfig._(app: app); + _firebaseRemoteConfigInstances[app.name] = newInstance; + + return newInstance; + } + + /// Returns the [DateTime] of the last successful fetch. + /// + /// If no successful fetch has been made a [DateTime] representing + /// the epoch (1970-01-01 UTC) is returned. + DateTime get lastFetchTime { + return _delegate.lastFetchTime; + } + + /// Returns the status of the last fetch attempt. + RemoteConfigFetchStatus get lastFetchStatus { + return _delegate.lastFetchStatus; + } + + /// Returns the [RemoteConfigSettings] of the current instance. + RemoteConfigSettings get settings { + return _delegate.settings; + } + + /// Makes the last fetched config available to getters. + /// + /// Returns a [bool] that is true if the config parameters + /// were activated. Returns a [bool] that is false if the + /// config parameters were already activated. + Future activate() async { + bool configChanged = await _delegate.activate(); + notifyListeners(); + return configChanged; + } + + /// Ensures the last activated config are available to getters. + Future ensureInitialized() { + return _delegate.ensureInitialized(); + } + + /// Fetches and caches configuration from the Remote Config service. + Future fetch() { + return _delegate.fetch(); + } + + /// Performs a fetch and activate operation, as a convenience. + /// + /// Returns [bool] in the same way that is done for [activate]. + Future fetchAndActivate() async { + bool configChanged = await _delegate.fetchAndActivate(); + notifyListeners(); + return configChanged; + } + + /// Returns a Map of all Remote Config parameters. + Map getAll() { + return _delegate.getAll(); + } + + /// Gets the value for a given key as a bool. + bool getBool(String key) { + assert(key != null); + return _delegate.getBool(key); + } + + /// Gets the value for a given key as an int. + int getInt(String key) { + assert(key != null); + return _delegate.getInt(key); + } + + /// Gets the value for a given key as a double. + double getDouble(String key) { + assert(key != null); + return _delegate.getDouble(key); + } + + /// Gets the value for a given key as a String. + String getString(String key) { + assert(key != null); + return _delegate.getString(key); + } + + /// Gets the [RemoteConfigValue] for a given key. + RemoteConfigValue getValue(String key) { + assert(key != null); + return _delegate.getValue(key); + } + + /// Sets the [RemoteConfigSettings] for the current instance. + Future setConfigSettings(RemoteConfigSettings remoteConfigSettings) { + assert(remoteConfigSettings != null); + assert(remoteConfigSettings.fetchTimeout != null); + assert(remoteConfigSettings.minimumFetchInterval != null); + assert(!remoteConfigSettings.fetchTimeout.isNegative); + assert(!remoteConfigSettings.minimumFetchInterval.isNegative); + // To be consistent with iOS fetchTimeout is set to the default + // 1 minute (60 seconds) if an attempt is made to set it to zero seconds. + if (remoteConfigSettings.fetchTimeout.inSeconds == 0) { + remoteConfigSettings.fetchTimeout = Duration(seconds: 60); + } + return _delegate.setConfigSettings(remoteConfigSettings); + } + + /// Sets the default parameter values for the current instance. + Future setDefaults(Map defaultParameters) { + assert(defaultParameters != null); + return _delegate.setDefaults(defaultParameters); + } +} diff --git a/packages/firebase_remote_config/macos/Assets/.gitkeep b/packages/firebase_remote_config/firebase_remote_config/macos/Assets/.gitkeep similarity index 100% rename from packages/firebase_remote_config/macos/Assets/.gitkeep rename to packages/firebase_remote_config/firebase_remote_config/macos/Assets/.gitkeep diff --git a/packages/firebase_remote_config/firebase_remote_config/macos/Classes/FLTFirebaseRemoteConfigPlugin.h b/packages/firebase_remote_config/firebase_remote_config/macos/Classes/FLTFirebaseRemoteConfigPlugin.h new file mode 120000 index 000000000000..05526e815d21 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config/macos/Classes/FLTFirebaseRemoteConfigPlugin.h @@ -0,0 +1 @@ +../../ios/Classes/FLTFirebaseRemoteConfigPlugin.h \ No newline at end of file diff --git a/packages/firebase_remote_config/firebase_remote_config/macos/Classes/FLTFirebaseRemoteConfigPlugin.m b/packages/firebase_remote_config/firebase_remote_config/macos/Classes/FLTFirebaseRemoteConfigPlugin.m new file mode 120000 index 000000000000..49d25bc2fc78 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config/macos/Classes/FLTFirebaseRemoteConfigPlugin.m @@ -0,0 +1 @@ +../../ios/Classes/FLTFirebaseRemoteConfigPlugin.m \ No newline at end of file diff --git a/packages/firebase_remote_config/firebase_remote_config/macos/Classes/FLTFirebaseRemoteConfigUtils.h b/packages/firebase_remote_config/firebase_remote_config/macos/Classes/FLTFirebaseRemoteConfigUtils.h new file mode 120000 index 000000000000..8d0f01de78c2 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config/macos/Classes/FLTFirebaseRemoteConfigUtils.h @@ -0,0 +1 @@ +../../ios/Classes/FLTFirebaseRemoteConfigUtils.h \ No newline at end of file diff --git a/packages/firebase_remote_config/firebase_remote_config/macos/Classes/FLTFirebaseRemoteConfigUtils.m b/packages/firebase_remote_config/firebase_remote_config/macos/Classes/FLTFirebaseRemoteConfigUtils.m new file mode 120000 index 000000000000..86538ce8c0a9 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config/macos/Classes/FLTFirebaseRemoteConfigUtils.m @@ -0,0 +1 @@ +../../ios/Classes/FLTFirebaseRemoteConfigUtils.m \ No newline at end of file diff --git a/packages/firebase_remote_config/macos/firebase_remote_config.podspec b/packages/firebase_remote_config/firebase_remote_config/macos/firebase_remote_config.podspec similarity index 100% rename from packages/firebase_remote_config/macos/firebase_remote_config.podspec rename to packages/firebase_remote_config/firebase_remote_config/macos/firebase_remote_config.podspec diff --git a/packages/firebase_remote_config/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config/pubspec.yaml similarity index 72% rename from packages/firebase_remote_config/pubspec.yaml rename to packages/firebase_remote_config/firebase_remote_config/pubspec.yaml index f012a34e082a..f425397bf1f5 100644 --- a/packages/firebase_remote_config/pubspec.yaml +++ b/packages/firebase_remote_config/firebase_remote_config/pubspec.yaml @@ -2,12 +2,14 @@ name: firebase_remote_config description: Flutter plugin for Firebase Remote Config. Update your application look and feel and behaviour without re-releasing. homepage: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_remote_config -version: 0.6.0 +version: 0.7.0 dependencies: flutter: sdk: flutter firebase_core: ^0.7.0 + firebase_core_platform_interface: ^3.0.1 + firebase_remote_config_platform_interface: ^0.0.1 dev_dependencies: pedantic: ^1.8.0 flutter_test: @@ -15,6 +17,8 @@ dev_dependencies: flutter_driver: sdk: flutter test: any + plugin_platform_interface: ^1.0.2 + mockito: ^4.1.1 flutter: plugin: @@ -23,9 +27,9 @@ flutter: package: io.flutter.plugins.firebase.firebaseremoteconfig pluginClass: FirebaseRemoteConfigPlugin ios: - pluginClass: FirebaseRemoteConfigPlugin + pluginClass: FLTFirebaseRemoteConfigPlugin macos: - pluginClass: FirebaseRemoteConfigPlugin + pluginClass: FLTFirebaseRemoteConfigPlugin environment: sdk: ">=2.0.0 <3.0.0" diff --git a/packages/firebase_remote_config/firebase_remote_config/test/firebase_remote_config_test.dart b/packages/firebase_remote_config/firebase_remote_config/test/firebase_remote_config_test.dart new file mode 100644 index 000000000000..770284efb2f7 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config/test/firebase_remote_config_test.dart @@ -0,0 +1,271 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:firebase_remote_config_platform_interface/firebase_remote_config_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'package:mockito/mockito.dart'; + +import 'mock.dart'; + +MockFirebaseRemoteConfig mockRemoteConfigPlatform = MockFirebaseRemoteConfig(); + +void main() { + setupFirebaseRemoteConfigMocks(); + + RemoteConfig remoteConfig; + + DateTime mockLastFetchTime; + RemoteConfigFetchStatus mockLastFetchStatus; + RemoteConfigSettings mockRemoteConfigSettings; + Map mockParameters; + Map mockDefaultParameters; + RemoteConfigValue mockRemoteConfigValue; + + group('$RemoteConfig', () { + FirebaseRemoteConfigPlatform.instance = mockRemoteConfigPlatform; + + setUpAll(() async { + await Firebase.initializeApp(); + remoteConfig = RemoteConfig.instance; + + mockLastFetchTime = DateTime(2020, 1, 1); + mockLastFetchStatus = RemoteConfigFetchStatus.noFetchYet; + mockRemoteConfigSettings = RemoteConfigSettings( + fetchTimeout: Duration(seconds: 10), + minimumFetchInterval: Duration(hours: 1), + ); + mockParameters = {}; + mockDefaultParameters = {}; + mockRemoteConfigValue = RemoteConfigValue( + [], + ValueSource.valueStatic, + ); + + when(mockRemoteConfigPlatform.instanceFor( + app: anyNamed('app'), + pluginConstants: anyNamed('pluginConstants'))) + .thenAnswer((_) => mockRemoteConfigPlatform); + + when(mockRemoteConfigPlatform.delegateFor( + app: anyNamed('app'), + )).thenAnswer((_) => mockRemoteConfigPlatform); + + when(mockRemoteConfigPlatform.setInitialValues( + remoteConfigValues: anyNamed('remoteConfigValues'))) + .thenAnswer((_) => mockRemoteConfigPlatform); + + when(mockRemoteConfigPlatform.lastFetchTime) + .thenReturn(mockLastFetchTime); + + when(mockRemoteConfigPlatform.lastFetchStatus) + .thenReturn(mockLastFetchStatus); + + when(mockRemoteConfigPlatform.settings) + .thenReturn(mockRemoteConfigSettings); + + when(mockRemoteConfigPlatform.setConfigSettings(any)) + .thenAnswer((_) => null); + + when(mockRemoteConfigPlatform.activate()) + .thenAnswer((_) => Future.value(true)); + + when(mockRemoteConfigPlatform.ensureInitialized()) + .thenAnswer((_) => Future.value()); + + when(mockRemoteConfigPlatform.fetch()).thenAnswer((_) => Future.value()); + + when(mockRemoteConfigPlatform.fetchAndActivate()) + .thenAnswer((_) => Future.value(true)); + + when(mockRemoteConfigPlatform.getAll()).thenReturn(mockParameters); + + when(mockRemoteConfigPlatform.getBool('foo')).thenReturn(true); + + when(mockRemoteConfigPlatform.getInt('foo')).thenReturn(8); + + when(mockRemoteConfigPlatform.getDouble('foo')).thenReturn(8.8); + + when(mockRemoteConfigPlatform.getString('foo')).thenReturn('bar'); + + when(mockRemoteConfigPlatform.getValue('foo')) + .thenReturn(mockRemoteConfigValue); + + when(mockRemoteConfigPlatform.setDefaults(any)) + .thenAnswer((_) => Future.value()); + }); + + test('doubleInstance', () async { + final List remoteConfigs = [ + RemoteConfig.instance, + RemoteConfig.instance, + ]; + expect(remoteConfigs[0], remoteConfigs[1]); + }); + + group('lastFetchTime', () { + test('get lastFetchTime', () { + remoteConfig.lastFetchTime; + verify(mockRemoteConfigPlatform.lastFetchTime); + }); + }); + + group('lastFetchStatus', () { + test('get lastFetchStatus', () { + remoteConfig.lastFetchStatus; + verify(mockRemoteConfigPlatform.lastFetchStatus); + }); + }); + + group('settings', () { + test('get settings', () { + remoteConfig.settings; + verify(mockRemoteConfigPlatform.settings); + }); + + test('set settings', () async { + final remoteConfigSettings = RemoteConfigSettings( + fetchTimeout: Duration(seconds: 8), + minimumFetchInterval: Duration.zero, + ); + await remoteConfig.setConfigSettings(remoteConfigSettings); + verify( + mockRemoteConfigPlatform.setConfigSettings(remoteConfigSettings)); + }); + + test('should throw if settings is null', () async { + expect( + () => remoteConfig.setConfigSettings(null), + throwsAssertionError, + ); + }); + }); + + group('activate()', () { + test('should call delegate method', () async { + await remoteConfig.activate(); + verify(mockRemoteConfigPlatform.activate()); + }); + }); + + group('ensureEnitialized()', () { + test('should call delegate method', () async { + await remoteConfig.ensureInitialized(); + verify(mockRemoteConfigPlatform.ensureInitialized()); + }); + }); + + group('fetch()', () { + test('should call delegate method', () async { + await remoteConfig.fetch(); + verify(mockRemoteConfigPlatform.fetch()); + }); + }); + + group('fetchAndActivate()', () { + test('should call delegate method', () async { + await remoteConfig.fetchAndActivate(); + verify(mockRemoteConfigPlatform.fetchAndActivate()); + }); + }); + + group('getAll()', () { + test('should call delegate method', () { + remoteConfig.getAll(); + verify(mockRemoteConfigPlatform.getAll()); + }); + }); + + group('getBool()', () { + test('should call delegate method', () { + remoteConfig.getBool('foo'); + verify(mockRemoteConfigPlatform.getBool('foo')); + }); + + test('should throw if key is null', () { + expect(() => remoteConfig.getBool(null), throwsAssertionError); + }); + }); + + group('getInt()', () { + test('should call delegate method', () { + remoteConfig.getInt('foo'); + verify(mockRemoteConfigPlatform.getInt('foo')); + }); + + test('should throw if key is null', () { + expect(() => remoteConfig.getInt(null), throwsAssertionError); + }); + }); + + group('getDouble()', () { + test('should call delegate method', () { + remoteConfig.getDouble('foo'); + verify(mockRemoteConfigPlatform.getDouble('foo')); + }); + + test('should throw if key is null', () { + expect(() => remoteConfig.getDouble(null), throwsAssertionError); + }); + }); + + group('getString()', () { + test('should call delegate method', () { + remoteConfig.getString('foo'); + verify(mockRemoteConfigPlatform.getString('foo')); + }); + + test('should throw if key is null', () { + expect(() => remoteConfig.getString(null), throwsAssertionError); + }); + }); + + group('getValue()', () { + test('should call delegate method', () { + remoteConfig.getValue('foo'); + verify(mockRemoteConfigPlatform.getValue('foo')); + }); + + test('should throw if key is null', () { + expect(() => remoteConfig.getValue(null), throwsAssertionError); + }); + }); + + group('setDefaults()', () { + test('should call delegate method', () { + remoteConfig.setDefaults(mockParameters); + verify(mockRemoteConfigPlatform.setDefaults(mockDefaultParameters)); + }); + + test('should throw if parameters are null', () { + expect(() => remoteConfig.setDefaults(null), throwsAssertionError); + }); + }); + }); +} + +class MockFirebaseRemoteConfig extends Mock + with MockPlatformInterfaceMixin + implements TestFirebaseRemoteConfigPlatform { + MockFirebaseRemoteConfig(); +} + +class TestFirebaseRemoteConfigPlatform extends FirebaseRemoteConfigPlatform { + TestFirebaseRemoteConfigPlatform() : super(); + + instanceFor({FirebaseApp app, Map pluginConstants}) {} + + FirebaseRemoteConfigPlatform delegateFor({FirebaseApp app}) { + return this; + } + + @override + FirebaseRemoteConfigPlatform setInitialValues( + {Map remoteConfigValues}) { + return this; + } +} diff --git a/packages/firebase_remote_config/firebase_remote_config/test/mock.dart b/packages/firebase_remote_config/firebase_remote_config/test/mock.dart new file mode 100644 index 000000000000..8878a4b46be4 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config/test/mock.dart @@ -0,0 +1,40 @@ +import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +typedef Callback(MethodCall call); + +setupFirebaseRemoteConfigMocks([Callback customHandlers]) { + TestWidgetsFlutterBinding.ensureInitialized(); + + MethodChannelFirebase.channel.setMockMethodCallHandler((call) async { + if (call.method == 'Firebase#initializeCore') { + return [ + { + 'name': defaultFirebaseAppName, + 'options': { + 'apiKey': '123', + 'appId': '123', + 'messagingSenderId': '123', + 'projectId': '123', + }, + 'pluginConstants': {}, + } + ]; + } + + if (call.method == 'Firebase#initializeApp') { + return { + 'name': call.arguments['appName'], + 'options': call.arguments['options'], + 'pluginConstants': {}, + }; + } + + if (customHandlers != null) { + customHandlers(call); + } + + return null; + }); +} diff --git a/packages/firebase_remote_config/firebase_remote_config_platform_interface/CHANGELOG.md b/packages/firebase_remote_config/firebase_remote_config_platform_interface/CHANGELOG.md new file mode 100644 index 000000000000..878c277f8f3e --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_platform_interface/CHANGELOG.md @@ -0,0 +1,3 @@ +# 0.0.1 + +Initial release diff --git a/packages/firebase_remote_config/firebase_remote_config_platform_interface/LICENSE b/packages/firebase_remote_config/firebase_remote_config_platform_interface/LICENSE new file mode 100644 index 000000000000..8940a4be1b58 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_platform_interface/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/firebase_remote_config/firebase_remote_config_platform_interface/README.md b/packages/firebase_remote_config/firebase_remote_config_platform_interface/README.md new file mode 100644 index 000000000000..3fa77ef09fd7 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_platform_interface/README.md @@ -0,0 +1,26 @@ +# firebase_remote_config_platform_interface + +A common platform interface for the [`firebase_remote_config`][1] plugin. + +This interface allows platform-specific implementations of the `firebase_remote_config` +plugin, as well as the plugin itself, to ensure they are supporting the +same interface. + +# Usage + +To implement a new platform-specific implementation of `firebase_remote_config`, extend +[`FirebaseRemoteConfigPlatform`][2] with an implementation that performs the +platform-specific behavior, and when you register your plugin, set the default +`FirebaseRemoteConfigPlatform` by calling +`FirebaseRemoteConfigPlatform.instance = MyRemoteConfig()`. + +# Note on breaking changes + +Strongly prefer non-breaking changes (such as adding a method to the interface) +over breaking changes for this package. + +See https://flutter.dev/go/platform-interface-breaking-changes for a discussion +on why a less-clean interface is preferable to a breaking change. + +[1]: ../firebase_remote_config +[2]: lib/firebase_remote_config_platform_interface.dart diff --git a/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/firebase_remote_config_platform_interface.dart b/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/firebase_remote_config_platform_interface.dart new file mode 100644 index 000000000000..0749743c2049 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/firebase_remote_config_platform_interface.dart @@ -0,0 +1,6 @@ +library firebase_remote_config_platform_interface; + +export 'src/platform_interface/platform_interface_firebase_remote_config.dart'; +export 'src/remote_config_settings.dart'; +export 'src/remote_config_value.dart'; +export 'src/remote_config_status.dart'; diff --git a/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/src/method_channel/method_channel_firebase_remote_config.dart b/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/src/method_channel/method_channel_firebase_remote_config.dart new file mode 100644 index 000000000000..179572b0def7 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/src/method_channel/method_channel_firebase_remote_config.dart @@ -0,0 +1,289 @@ +import 'dart:async'; + +import 'package:firebase_remote_config_platform_interface/firebase_remote_config_platform_interface.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/services.dart'; +import 'package:meta/meta.dart'; + +import 'utils/exception.dart'; + +/// Method Channel delegate for [FirebaseRemoteConfigPlatform]. +class MethodChannelFirebaseRemoteConfig extends FirebaseRemoteConfigPlatform { + /// Keeps an internal handle ID for the channel. + static int _methodChannelHandleId = 0; + + /// Increments and returns the next channel ID handler for RemoteConfig. + static int get nextMethodChannelHandleId => _methodChannelHandleId++; + + /// The [MethodChannelRemoteConfig] method channel. + static const MethodChannel channel = + MethodChannel('plugins.flutter.io/firebase_remote_config'); + + static Map + _methodChannelFirebaseRemoteConfigInstances = + {}; + + /// Returns a stub instance to allow the platform interface to access + /// the class instance statically. + static MethodChannelFirebaseRemoteConfig get instance { + return MethodChannelFirebaseRemoteConfig._(); + } + + /// Internal stub class initializer. + /// + /// When the user code calls a Remote Config method, the real instance + /// is initialized via the [delegateFor] method. + MethodChannelFirebaseRemoteConfig._() : super(appInstance: null); + + /// Creates a new instance for a given [FirebaseApp]. + MethodChannelFirebaseRemoteConfig({@required FirebaseApp app}) + : super(appInstance: app); + + Map _activeParameters; + RemoteConfigSettings _settings; + DateTime _lastFetchTime; + RemoteConfigFetchStatus _lastFetchStatus; + + /// Gets a [FirebaseRemoteConfigPlatform] instance for a specific + /// [FirebaseApp]. + /// + /// Instances are cached and reused for incoming event handlers. + @override + FirebaseRemoteConfigPlatform delegateFor({FirebaseApp app}) { + if (_methodChannelFirebaseRemoteConfigInstances.containsKey(app.name)) { + return _methodChannelFirebaseRemoteConfigInstances[app.name]; + } + + _methodChannelFirebaseRemoteConfigInstances[app.name] = + MethodChannelFirebaseRemoteConfig(app: app); + return _methodChannelFirebaseRemoteConfigInstances[app.name]; + } + + @override + FirebaseRemoteConfigPlatform setInitialValues( + {Map remoteConfigValues}) { + final fetchTimeout = Duration(seconds: remoteConfigValues['fetchTimeout']); + final minimumFetchInterval = + Duration(seconds: remoteConfigValues['minimumFetchInterval']); + final lastFetchMillis = remoteConfigValues['lastFetchTime']; + final lastFetchStatus = remoteConfigValues['lastFetchStatus']; + + _settings = RemoteConfigSettings( + fetchTimeout: fetchTimeout, + minimumFetchInterval: minimumFetchInterval, + ); + _lastFetchTime = DateTime.fromMillisecondsSinceEpoch(lastFetchMillis); + _lastFetchStatus = _parseFetchStatus(lastFetchStatus); + _activeParameters = _parseParameters(remoteConfigValues['parameters']); + return this; + } + + RemoteConfigFetchStatus _parseFetchStatus(String status) { + switch (status) { + case 'noFetchYet': + return RemoteConfigFetchStatus.noFetchYet; + case 'success': + return RemoteConfigFetchStatus.success; + case 'failure': + return RemoteConfigFetchStatus.failure; + case 'throttle': + return RemoteConfigFetchStatus.throttle; + default: + return RemoteConfigFetchStatus.noFetchYet; + } + } + + @override + DateTime get lastFetchTime => _lastFetchTime; + + @override + RemoteConfigFetchStatus get lastFetchStatus => _lastFetchStatus; + + @override + RemoteConfigSettings get settings => _settings; + + @override + Future ensureInitialized() async { + try { + await channel.invokeMethod( + 'RemoteConfig#ensureInitialized', { + 'appName': app.name, + }); + } catch (exception, stackTrace) { + throw convertPlatformException(exception, stackTrace); + } + } + + @override + Future activate() async { + try { + bool configChanged = await channel + .invokeMethod('RemoteConfig#activate', { + 'appName': app.name, + }); + await _updateConfigParameters(); + return configChanged; + } catch (exception, stackTrace) { + throw convertPlatformException(exception, stackTrace); + } + } + + @override + Future fetch() async { + try { + await channel.invokeMethod('RemoteConfig#fetch', { + 'appName': app.name, + }); + await _updateConfigProperties(); + } catch (exception, stackTrace) { + // Ensure that fetch status is updated. + await _updateConfigProperties(); + throw convertPlatformException(exception, stackTrace); + } + } + + @override + Future fetchAndActivate() async { + try { + bool configChanged = await channel.invokeMethod( + 'RemoteConfig#fetchAndActivate', { + 'appName': app.name, + }); + await _updateConfigParameters(); + await _updateConfigProperties(); + return configChanged; + } catch (exception, stackTrace) { + // Ensure that fetch status is updated. + await _updateConfigProperties(); + throw convertPlatformException(exception, stackTrace); + } + } + + @override + Map getAll() { + return _activeParameters; + } + + @override + bool getBool(String key) { + if (!_activeParameters.containsKey(key)) { + return RemoteConfigValue.defaultValueForBool; + } + return _activeParameters[key].asBool(); + } + + @override + int getInt(String key) { + if (!_activeParameters.containsKey(key)) { + return RemoteConfigValue.defaultValueForInt; + } + return _activeParameters[key].asInt(); + } + + @override + double getDouble(String key) { + if (!_activeParameters.containsKey(key)) { + return RemoteConfigValue.defaultValueForDouble; + } + return _activeParameters[key].asDouble(); + } + + @override + String getString(String key) { + if (!_activeParameters.containsKey(key)) { + return RemoteConfigValue.defaultValueForString; + } + return _activeParameters[key].asString(); + } + + @override + RemoteConfigValue getValue(String key) { + if (!_activeParameters.containsKey(key)) { + return RemoteConfigValue(null, ValueSource.valueStatic); + } + return _activeParameters[key]; + } + + @override + Future setConfigSettings( + RemoteConfigSettings remoteConfigSettings) async { + try { + await channel + .invokeMethod('RemoteConfig#setConfigSettings', { + 'appName': app.name, + 'fetchTimeout': remoteConfigSettings.fetchTimeout.inSeconds, + 'minimumFetchInterval': + remoteConfigSettings.minimumFetchInterval.inSeconds, + }); + await _updateConfigProperties(); + } catch (exception, stackTrace) { + throw convertPlatformException(exception, stackTrace); + } + } + + @override + Future setDefaults(Map defaultParameters) async { + try { + await channel.invokeMethod('RemoteConfig#setDefaults', { + 'appName': app.name, + 'defaults': defaultParameters + }); + await _updateConfigParameters(); + } catch (exception, stackTrace) { + throw convertPlatformException(exception, stackTrace); + } + } + + Future _updateConfigParameters() async { + Map parameters = await channel + .invokeMapMethod( + 'RemoteConfig#getAll', { + 'appName': app.name, + }); + _activeParameters = _parseParameters(parameters); + } + + Future _updateConfigProperties() async { + Map properties = await channel + .invokeMapMethod( + 'RemoteConfig#getProperties', { + 'appName': app.name, + }); + final fetchTimeout = Duration(seconds: properties['fetchTimeout']); + final minimumFetchInterval = + Duration(seconds: properties['minimumFetchInterval']); + final lastFetchMillis = properties['lastFetchTime']; + final lastFetchStatus = properties['lastFetchStatus']; + + _settings = RemoteConfigSettings( + fetchTimeout: fetchTimeout, + minimumFetchInterval: minimumFetchInterval, + ); + _lastFetchTime = DateTime.fromMillisecondsSinceEpoch(lastFetchMillis); + _lastFetchStatus = _parseFetchStatus(lastFetchStatus); + } + + Map _parseParameters( + Map rawParameters) { + Map parameters = Map(); + for (String key in rawParameters.keys) { + final rawValue = rawParameters[key]; + parameters[key] = RemoteConfigValue( + rawValue['value'], _parseValueSource(rawValue['source'])); + } + return parameters; + } + + ValueSource _parseValueSource(String sourceStr) { + switch (sourceStr) { + case 'static': + return ValueSource.valueStatic; + case 'default': + return ValueSource.valueDefault; + case 'remote': + return ValueSource.valueRemote; + default: + return ValueSource.valueStatic; + } + } +} diff --git a/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/src/method_channel/utils/exception.dart b/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/src/method_channel/utils/exception.dart new file mode 100644 index 000000000000..796479405126 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/src/method_channel/utils/exception.dart @@ -0,0 +1,43 @@ +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/services.dart'; + +/// Catches a [PlatformException] and converts it into a [FirebaseException] if +/// it was intentionally caught on the native platform. +Exception convertPlatformException(Object exception, [StackTrace stackTrace]) { + if (exception is! Exception || exception is! PlatformException) { + throw exception; + } + + return platformExceptionToFirebaseException( + exception as PlatformException, + stackTrace, + ); +} + +/// Converts a [PlatformException] into a [FirebaseException]. +/// +/// A [PlatformException] can only be converted to a [FirebaseException] if the +/// `details` of the exception exist. Firebase returns specific codes and messages +/// which can be converted into user friendly exceptions. +FirebaseException platformExceptionToFirebaseException( + PlatformException platformException, + [StackTrace stackTrace]) { + Map details = platformException.details != null + ? Map.from(platformException.details) + : null; + + String code = 'unknown'; + String message = platformException.message; + + if (details != null) { + code = details['code'] ?? code; + message = details['message'] ?? message; + } + + return FirebaseException( + plugin: 'firebase_remote_config', + code: code, + message: message, + stackTrace: stackTrace, + ); +} diff --git a/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/src/platform_interface/platform_interface_firebase_remote_config.dart b/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/src/platform_interface/platform_interface_firebase_remote_config.dart new file mode 100644 index 000000000000..a3bb6b0c9198 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/src/platform_interface/platform_interface_firebase_remote_config.dart @@ -0,0 +1,170 @@ +import 'dart:async'; + +import 'package:firebase_remote_config_platform_interface/firebase_remote_config_platform_interface.dart'; +import 'package:firebase_remote_config_platform_interface/src/method_channel/method_channel_firebase_remote_config.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +/// The interface that implementations of `firebase_remote_config` must +/// extend. +/// +/// Platform implementations should extend this class rather than implement it +/// as `firebase_remote_config` does not consider newly added methods to be breaking +/// changes. Extending this class (using `extends`) ensures that the subclass +/// will get the default implementation, while platform implementations that +/// `implements` this interface will be broken by newly added +/// [FirebaseRemoteConfigPlatform] methods. +abstract class FirebaseRemoteConfigPlatform extends PlatformInterface { + static final Object _token = Object(); + + /// The [FirebaseApp] this instance was initialized with. + @protected + final FirebaseApp appInstance; + + /// Create an instance using [app]. + FirebaseRemoteConfigPlatform({this.appInstance}) : super(token: _token); + + /// Returns the [FirebaseApp] for the current instance. + FirebaseApp get app { + if (appInstance == null) { + return Firebase.app(); + } + return appInstance; + } + + static FirebaseRemoteConfigPlatform _instance; + + /// The current default [FirebaseRemoteConfigPlatform] instance. + /// + /// It will always default to [MethodChannelFirebaseRemoteConfig] + /// if no other implementation was provided. + static FirebaseRemoteConfigPlatform get instance { + if (_instance == null) { + _instance = MethodChannelFirebaseRemoteConfig.instance; + } + + return _instance; + } + + /// Sets the [FirebaseRemoteConfigPlatform] instance. + static set instance(FirebaseRemoteConfigPlatform instance) { + assert(instance != null); + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + /// Create instance using [app] using the existing implementation. + factory FirebaseRemoteConfigPlatform.instanceFor( + {FirebaseApp app, Map pluginConstants}) { + return FirebaseRemoteConfigPlatform.instance + .delegateFor(app: app) + .setInitialValues( + remoteConfigValues: pluginConstants == null + ? Map() + : pluginConstants); + } + + /// Enables delegates to create new instances of themselves if a none + /// default [FirebaseApp] instance is required by the user. + @protected + FirebaseRemoteConfigPlatform delegateFor({FirebaseApp app}) { + throw UnimplementedError('delegateFor() is not implemented'); + } + + /// Sets any initial values on the instance. + /// + /// Platforms with Method Channels can provide constant values to be + /// available before the instance has initialized to prevent unnecessary + /// async calls. + @protected + FirebaseRemoteConfigPlatform setInitialValues( + {Map remoteConfigValues}) { + throw UnimplementedError('setInitialValues() is not implemented'); + } + + /// Returns the [DateTime] of the last successful fetch. + /// + /// If no successful fetch has been made a [DateTime] representing + /// the epoch (1970-01-01 UTC) is returned. + DateTime get lastFetchTime { + throw UnimplementedError('lastFetchTime getter not implemented'); + } + + /// Returns the status of the last fetch attempt. + RemoteConfigFetchStatus get lastFetchStatus { + throw UnimplementedError('lastFetchStatus getter not implemented'); + } + + /// Returns the [RemoteConfigSettings] of the current instance. + RemoteConfigSettings get settings { + throw UnimplementedError('settings getter not implemented'); + } + + /// Makes the last fetched config available to getters. + /// + /// Returns a [bool] that is true if the config parameters + /// were activated. Returns a [bool] that is false if the + /// config parameters were already activated. + Future activate() { + throw UnimplementedError('activate() is not implemented'); + } + + /// Ensures the last activated config are available to getters. + Future ensureInitialized() { + throw UnimplementedError('ensureInitialized() is not implemented'); + } + + /// Fetches and caches configuration from the Remote Config service. + Future fetch() { + throw UnimplementedError('fetch() is not implemented'); + } + + /// Performs a fetch and activate operation, as a convenience. + /// + /// Returns [bool] in the same way that is done for [activate]. + Future fetchAndActivate() { + throw UnimplementedError('fetchAndActivate() is not implemented'); + } + + /// Returns a Map of all Remote Config parameters. + Map getAll() { + throw UnimplementedError('getAll() is not implemented'); + } + + /// Gets the value for a given key as a bool. + bool getBool(String key) { + throw UnimplementedError('getBool() is not implemented'); + } + + /// Gets the value for a given key as an int. + int getInt(String key) { + throw UnimplementedError('getInt() is not implemented'); + } + + /// Gets the value for a given key as a double. + double getDouble(String key) { + throw UnimplementedError('getDouble() is not implemented'); + } + + /// Gets the value for a given key as a String. + String getString(String key) { + throw UnimplementedError('getString() is not implemented'); + } + + /// Gets the [RemoteConfigValue] for a given key. + RemoteConfigValue getValue(String key) { + throw UnimplementedError('getValue() is not implemented'); + } + + /// Sets the [RemoteConfigSettings] for the current instance. + Future setConfigSettings(RemoteConfigSettings remoteConfigSettings) { + throw UnimplementedError('setConfigSettings() is not implemented'); + } + + /// Sets the default parameter values for the current instance. + Future setDefaults(Map defaultParameters) { + throw UnimplementedError('setDefaults() is not implemented'); + } +} diff --git a/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/src/remote_config_settings.dart b/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/src/remote_config_settings.dart new file mode 100644 index 000000000000..abc3b481b164 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/src/remote_config_settings.dart @@ -0,0 +1,19 @@ +import 'package:meta/meta.dart'; + +/// Defines the options for the corresponding Remote Config instance. +class RemoteConfigSettings { + /// Constructs an instance of [RemoteConfigSettings] with given [fetchTimeout] + /// and [minimumFetchInterval]. + RemoteConfigSettings({ + @required this.fetchTimeout, + @required this.minimumFetchInterval, + }); + + /// Maximum Duration to wait for a response when fetching configuration from + /// the Remote Config server. Defaults to one minute. + Duration fetchTimeout; + + /// Maximum age of a cached config before it is considered stale. Defaults + /// to twelve hours. + Duration minimumFetchInterval; +} diff --git a/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/src/remote_config_status.dart b/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/src/remote_config_status.dart new file mode 100644 index 000000000000..c20900d0ff43 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/src/remote_config_status.dart @@ -0,0 +1,15 @@ +/// The outcome of the last attempt to fetch config from the +/// Firebase Remote Config server. +enum RemoteConfigFetchStatus { + /// Indicates instance has not yet attempted a fetch. + noFetchYet, + + /// Indicates the last fetch attempt succeeded. + success, + + /// Indicates the last fetch attempt failed. + failure, + + /// Indicates the last fetch attempt was rate-limited. + throttle +} diff --git a/packages/firebase_remote_config/lib/src/remote_config_value.dart b/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/src/remote_config_value.dart similarity index 50% rename from packages/firebase_remote_config/lib/src/remote_config_value.dart rename to packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/src/remote_config_value.dart index ddf93a5df189..28feadd307ce 100644 --- a/packages/firebase_remote_config/lib/src/remote_config_value.dart +++ b/packages/firebase_remote_config/firebase_remote_config_platform_interface/lib/src/remote_config_value.dart @@ -1,16 +1,37 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. +import 'dart:convert'; -part of firebase_remote_config; +import 'package:flutter/foundation.dart'; /// ValueSource defines the possible sources of a config parameter value. -enum ValueSource { valueStatic, valueDefault, valueRemote } +enum ValueSource { + /// The value was defined by a static constant. + valueStatic, + + /// The value was defined by default config. + valueDefault, + + /// The value was defined by fetched config. + valueRemote, +} /// RemoteConfigValue encapsulates the value and source of a Remote Config /// parameter. class RemoteConfigValue { - RemoteConfigValue._(this._value, this.source) : assert(source != null); + /// Default value for String + static const String defaultValueForString = ''; + + /// Default value for Int + static const int defaultValueForInt = 0; + + /// Default value for Double + static const double defaultValueForDouble = 0.0; + + /// Default value for Bool + static const bool defaultValueForBool = false; + + /// Wraps a value with metadata and type-safe getters. + @protected + RemoteConfigValue(this._value, this.source) : assert(source != null); List _value; @@ -21,18 +42,17 @@ class RemoteConfigValue { String asString() { return _value != null ? const Utf8Codec().decode(_value) - : RemoteConfig.defaultValueForString; + : defaultValueForString; } /// Decode value to int. int asInt() { if (_value != null) { final String strValue = const Utf8Codec().decode(_value); - final int intValue = - int.tryParse(strValue) ?? RemoteConfig.defaultValueForInt; + final int intValue = int.tryParse(strValue) ?? defaultValueForInt; return intValue; } else { - return RemoteConfig.defaultValueForInt; + return defaultValueForInt; } } @@ -41,10 +61,10 @@ class RemoteConfigValue { if (_value != null) { final String strValue = const Utf8Codec().decode(_value); final double doubleValue = - double.tryParse(strValue) ?? RemoteConfig.defaultValueForDouble; + double.tryParse(strValue) ?? defaultValueForDouble; return doubleValue; } else { - return RemoteConfig.defaultValueForDouble; + return defaultValueForDouble; } } @@ -54,7 +74,7 @@ class RemoteConfigValue { final String strValue = const Utf8Codec().decode(_value); return strValue.toLowerCase() == 'true'; } else { - return RemoteConfig.defaultValueForBool; + return defaultValueForBool; } } } diff --git a/packages/firebase_remote_config/firebase_remote_config_platform_interface/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config_platform_interface/pubspec.yaml new file mode 100644 index 000000000000..8e36cbaa420e --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_platform_interface/pubspec.yaml @@ -0,0 +1,24 @@ +name: firebase_remote_config_platform_interface +description: A common platform interface for the firebase_remote_config plugin. +homepage: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_remote_config/firebase_remote_config_platform_interface +# NOTE: We strongly prefer non-breaking changes, even at the expense of a +# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes +version: 0.0.1 + +dependencies: + flutter: + sdk: flutter + meta: ^1.0.5 + firebase_core: ^0.5.2 + plugin_platform_interface: ^1.0.1 + +dev_dependencies: + firebase_core_platform_interface: ^2.0.0 + pedantic: ^1.8.0 + flutter_test: + sdk: flutter + mockito: ^4.1.1 + +environment: + sdk: ">=2.0.0 <3.0.0" + flutter: ">=1.9.1+hotfix.5 <2.0.0" diff --git a/packages/firebase_remote_config/ios/Classes/FirebaseRemoteConfigPlugin.m b/packages/firebase_remote_config/ios/Classes/FirebaseRemoteConfigPlugin.m deleted file mode 100644 index afa9ed382fdb..000000000000 --- a/packages/firebase_remote_config/ios/Classes/FirebaseRemoteConfigPlugin.m +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "FirebaseRemoteConfigPlugin.h" - -#import - -@interface FirebaseRemoteConfigPlugin () -@property(nonatomic, retain) FlutterMethodChannel *channel; -@end - -@implementation FirebaseRemoteConfigPlugin - -+ (void)registerWithRegistrar:(NSObject *)registrar { - FlutterMethodChannel *channel = - [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/firebase_remote_config" - binaryMessenger:[registrar messenger]]; - FirebaseRemoteConfigPlugin *instance = [[FirebaseRemoteConfigPlugin alloc] init]; - [registrar addMethodCallDelegate:instance channel:channel]; - - SEL sel = NSSelectorFromString(@"registerLibrary:withVersion:"); - if ([FIRApp respondsToSelector:sel]) { - [FIRApp performSelector:sel withObject:LIBRARY_NAME withObject:LIBRARY_VERSION]; - } -} - -- (instancetype)init { - self = [super init]; - return self; -} - -- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { - if ([@"RemoteConfig#instance" isEqualToString:call.method]) { - FIRRemoteConfig *remoteConfig = [FIRRemoteConfig remoteConfig]; - FIRRemoteConfigSettings *firRemoteConfigSettings = [remoteConfig configSettings]; - NSMutableDictionary *resultDict = [[NSMutableDictionary alloc] init]; - - resultDict[@"lastFetchTime"] = [[NSNumber alloc] - initWithLong:(long)[[remoteConfig lastFetchTime] timeIntervalSince1970] * 1000]; - resultDict[@"lastFetchStatus"] = - [self mapLastFetchStatus:(FIRRemoteConfigFetchStatus)[remoteConfig lastFetchStatus]]; - resultDict[@"minimumFetchInterval"] = - [[NSNumber alloc] initWithLong:(long)[firRemoteConfigSettings minimumFetchInterval]]; - resultDict[@"fetchTimeout"] = - [[NSNumber alloc] initWithLong:(long)[firRemoteConfigSettings fetchTimeout]]; - - resultDict[@"parameters"] = [self getConfigParameters]; - - result(resultDict); - } else if ([@"RemoteConfig#setConfigSettings" isEqualToString:call.method]) { - FIRRemoteConfig *remoteConfig = [FIRRemoteConfig remoteConfig]; - FIRRemoteConfigSettings *remoteConfigSettings = [[FIRRemoteConfigSettings alloc] init]; - if ([call.arguments objectForKey:@"minimumFetchInterval"]) { - remoteConfigSettings.minimumFetchInterval = - [call.arguments[@"minimumFetchInterval"] longValue]; - } - if ([call.arguments objectForKey:@"fetchTimeout"]) { - remoteConfigSettings.fetchTimeout = [call.arguments[@"fetchTimeout"] longValue]; - } - [remoteConfig setConfigSettings:remoteConfigSettings]; - result(nil); - } else if ([@"RemoteConfig#fetch" isEqualToString:call.method]) { - FIRRemoteConfig *remoteConfig = [FIRRemoteConfig remoteConfig]; - long expiration = (long)call.arguments[@"expiration"]; - - [remoteConfig - fetchWithExpirationDuration:expiration - completionHandler:^(FIRRemoteConfigFetchStatus status, NSError *error) { - NSNumber *lastFetchTime = [[NSNumber alloc] - initWithLong:(long)[[remoteConfig lastFetchTime] timeIntervalSince1970] * - 1000]; - NSString *lastFetchStatus = - [self mapLastFetchStatus:(FIRRemoteConfigFetchStatus)[remoteConfig - lastFetchStatus]]; - NSMutableDictionary *resultDict = [[NSMutableDictionary alloc] init]; - resultDict[@"lastFetchTime"] = lastFetchTime; - resultDict[@"lastFetchStatus"] = lastFetchStatus; - - if (status != FIRRemoteConfigFetchStatusSuccess) { - FlutterError *flutterError; - if (status == FIRRemoteConfigFetchStatusThrottled) { - int mills = - [[error.userInfo - valueForKey:FIRRemoteConfigThrottledEndTimeInSecondsKey] intValue] * - 1000; - resultDict[@"fetchThrottledEnd"] = [[NSNumber alloc] initWithInt:mills]; - NSString *errorMessage = - @"Fetch has been throttled. See the error's fetchThrottledEnd " - "field for throttle end time."; - flutterError = [FlutterError errorWithCode:@"fetchFailedThrottled" - message:errorMessage - details:resultDict]; - } else { - NSString *errorMessage = @"Unable to complete fetch. Reason is unknown " - "but this could be due to lack of connectivity."; - flutterError = [FlutterError errorWithCode:@"fetchFailed" - message:errorMessage - details:resultDict]; - } - result(flutterError); - } else { - result(resultDict); - } - }]; - } else if ([@"RemoteConfig#activate" isEqualToString:call.method]) { - [[FIRRemoteConfig remoteConfig] - activateWithCompletion:^(BOOL changed, NSError *_Nullable error) { - BOOL newConfig = YES; - - // If the config was already activated, we get an error from the SDK. - // Our goal is to map that specific error to a normal return with "false" for newConfig - // All other errors are rethrown as actual errors. - if (error) { - NSString *failureReason = @""; - if (error.userInfo && error.userInfo[@"ActivationFailureReason"] != nil) { - failureReason = error.userInfo[@"ActivationFailureReason"]; - } - if ([failureReason containsString:@"already activated"]) { - newConfig = NO; - } else { - FlutterError *flutterError; - flutterError = [FlutterError errorWithCode:@"activateFailed" - message:failureReason - details:nil]; - result(flutterError); - return; - } - } - - // If no real error, return all configs with boolean indicating if newly activated - NSDictionary *parameters = [self getConfigParameters]; - NSMutableDictionary *resultDict = [[NSMutableDictionary alloc] init]; - resultDict[@"newConfig"] = [NSNumber numberWithBool:newConfig]; - resultDict[@"parameters"] = parameters; - result(resultDict); - }]; - } else if ([@"RemoteConfig#setDefaults" isEqualToString:call.method]) { - FIRRemoteConfig *remoteConfig = [FIRRemoteConfig remoteConfig]; - NSDictionary *defaults = call.arguments[@"defaults"]; - [remoteConfig setDefaults:defaults]; - result(nil); - } else { - result(FlutterMethodNotImplemented); - } -} - -- (NSMutableDictionary *)createRemoteConfigValueDict:(FIRRemoteConfigValue *)remoteConfigValue { - NSMutableDictionary *valueDict = [[NSMutableDictionary alloc] init]; - valueDict[@"value"] = [FlutterStandardTypedData typedDataWithBytes:[remoteConfigValue dataValue]]; - valueDict[@"source"] = [self mapValueSource:[remoteConfigValue source]]; - return valueDict; -} - -- (NSDictionary *)getConfigParameters { - FIRRemoteConfig *remoteConfig = [FIRRemoteConfig remoteConfig]; - NSMutableDictionary *parameterDict = [[NSMutableDictionary alloc] init]; - NSSet *keySet = [remoteConfig keysWithPrefix:@""]; - for (NSString *key in keySet) { - parameterDict[key] = [self createRemoteConfigValueDict:[remoteConfig configValueForKey:key]]; - } - // Add default parameters if missing since `keysWithPrefix` does not return default keys. - NSArray *defaultKeys = [remoteConfig allKeysFromSource:FIRRemoteConfigSourceDefault]; - for (NSString *key in defaultKeys) { - if ([parameterDict valueForKey:key] == nil) { - parameterDict[key] = [self createRemoteConfigValueDict:[remoteConfig configValueForKey:key]]; - } - } - return parameterDict; -} - -- (NSString *)mapLastFetchStatus:(FIRRemoteConfigFetchStatus)status { - if (status == FIRRemoteConfigFetchStatusSuccess) { - return @"success"; - } else if (status == FIRRemoteConfigFetchStatusFailure) { - return @"failure"; - } else if (status == FIRRemoteConfigFetchStatusThrottled) { - return @"throttled"; - } else if (status == FIRRemoteConfigFetchStatusNoFetchYet) { - return @"noFetchYet"; - } else { - return @"failure"; - } -} - -- (NSString *)mapValueSource:(FIRRemoteConfigSource)source { - if (source == FIRRemoteConfigSourceStatic) { - return @"static"; - } else if (source == FIRRemoteConfigSourceDefault) { - return @"default"; - } else if (source == FIRRemoteConfigSourceRemote) { - return @"remote"; - } else { - return @"static"; - } -} - -@end diff --git a/packages/firebase_remote_config/lib/firebase_remote_config.dart b/packages/firebase_remote_config/lib/firebase_remote_config.dart deleted file mode 100644 index 641e6efbe102..000000000000 --- a/packages/firebase_remote_config/lib/firebase_remote_config.dart +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -library firebase_remote_config; - -import 'dart:async'; -import 'dart:convert'; - -import 'package:flutter/services.dart'; -import 'package:flutter/foundation.dart'; - -part 'src/remote_config.dart'; -part 'src/remote_config_settings.dart'; -part 'src/remote_config_value.dart'; -part 'src/remote_config_fetch_throttled_exception.dart'; -part 'src/remote_config_last_fetch_status.dart'; diff --git a/packages/firebase_remote_config/lib/src/remote_config.dart b/packages/firebase_remote_config/lib/src/remote_config.dart deleted file mode 100644 index 8a788aba7fb4..000000000000 --- a/packages/firebase_remote_config/lib/src/remote_config.dart +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -part of firebase_remote_config; - -/// The entry point for accessing Remote Config. -/// -/// You can get an instance by calling [RemoteConfig.instance]. Note -/// [RemoteConfig.instance] is async. -class RemoteConfig extends ChangeNotifier { - @visibleForTesting - static const MethodChannel channel = - MethodChannel('plugins.flutter.io/firebase_remote_config'); - - static const String defaultValueForString = ''; - static const int defaultValueForInt = 0; - static const double defaultValueForDouble = 0.0; - static const bool defaultValueForBool = false; - - Map _parameters; - - DateTime _lastFetchTime; - LastFetchStatus _lastFetchStatus; - RemoteConfigSettings _remoteConfigSettings; - - DateTime get lastFetchTime => _lastFetchTime; - LastFetchStatus get lastFetchStatus => _lastFetchStatus; - RemoteConfigSettings get remoteConfigSettings => _remoteConfigSettings; - - static Completer _instanceCompleter = Completer(); - - /// Gets the instance of RemoteConfig for the default Firebase app. - static Future get instance async { - if (!_instanceCompleter.isCompleted) { - _instanceCompleter.complete(await _getRemoteConfigInstance()); - } - return _instanceCompleter.future; - } - - static Future _getRemoteConfigInstance() async { - final Map properties = - await channel.invokeMapMethod('RemoteConfig#instance'); - - final RemoteConfig instance = RemoteConfig(); - - instance._lastFetchTime = - DateTime.fromMillisecondsSinceEpoch(properties['lastFetchTime']); - instance._lastFetchStatus = - _parseLastFetchStatus(properties['lastFetchStatus']); - final RemoteConfigSettings remoteConfigSettings = RemoteConfigSettings( - minimumFetchIntervalMillis: (properties['minimumFetchInterval'] * 1000), - fetchTimeoutMillis: (properties['fetchTimeout'] * 1000)); - instance._remoteConfigSettings = remoteConfigSettings; - instance._parameters = - _parseRemoteConfigParameters(parameters: properties['parameters']); - return instance; - } - - static Map _parseRemoteConfigParameters( - {Map parameters}) { - final Map parsedParameters = - {}; - parameters.forEach((dynamic key, dynamic value) { - final ValueSource valueSource = _parseValueSource(value['source']); - final RemoteConfigValue remoteConfigValue = - RemoteConfigValue._(value['value']?.cast(), valueSource); - parsedParameters[key] = remoteConfigValue; - }); - return parsedParameters; - } - - static ValueSource _parseValueSource(String sourceStr) { - switch (sourceStr) { - case 'static': - return ValueSource.valueStatic; - case 'default': - return ValueSource.valueDefault; - case 'remote': - return ValueSource.valueRemote; - default: - return null; - } - } - - static LastFetchStatus _parseLastFetchStatus(String statusStr) { - switch (statusStr) { - case 'success': - return LastFetchStatus.success; - case 'failure': - return LastFetchStatus.failure; - case 'throttled': - return LastFetchStatus.throttled; - case 'noFetchYet': - return LastFetchStatus.noFetchYet; - default: - return LastFetchStatus.failure; - } - } - - /// Set the configuration settings for this [RemoteConfig] instance. - /// - /// This can be used to set minimum fetch time and fetch timeout. - Future setConfigSettings( - RemoteConfigSettings remoteConfigSettings) async { - await channel - .invokeMethod('RemoteConfig#setConfigSettings', { - 'minimumFetchInterval': - (remoteConfigSettings.minimumFetchIntervalMillis ~/ 1000), - 'fetchTimeout': (remoteConfigSettings.fetchTimeoutMillis ~/ 1000) - }); - _remoteConfigSettings = remoteConfigSettings; - } - - /// Fetches parameter values for your app. - /// - /// Parameter values may be from Default Config (local cache) or Remote - /// Config if enough time has elapsed since parameter values were last - /// fetched from the server. The default expiration time is 12 hours. - /// Expiration must be defined in seconds. - Future fetch({Duration expiration = const Duration(hours: 12)}) async { - try { - final Map properties = await channel - .invokeMapMethod('RemoteConfig#fetch', - {'expiration': expiration.inSeconds}); - _lastFetchTime = - DateTime.fromMillisecondsSinceEpoch(properties['lastFetchTime']); - _lastFetchStatus = _parseLastFetchStatus(properties['lastFetchStatus']); - } on PlatformException catch (e) { - _lastFetchTime = - DateTime.fromMillisecondsSinceEpoch(e.details['lastFetchTime']); - _lastFetchStatus = _parseLastFetchStatus(e.details['lastFetchStatus']); - if (e.code == 'fetchFailedThrottled') { - final int fetchThrottleEnd = e.details['fetchThrottledEnd']; - throw FetchThrottledException._(endTimeInMills: fetchThrottleEnd); - } else { - throw Exception(e.message); - } - } - } - - /// Activates the fetched config, makes fetched key-values take effect. - /// - /// The returned Future completes true if the fetched config is different - /// from the currently activated config, it contains false otherwise. - Future activateFetched() async { - final Map properties = - await channel.invokeMapMethod('RemoteConfig#activate'); - final Map rawParameters = properties['parameters']; - final bool newConfig = properties['newConfig']; - final Map fetchedParameters = - _parseRemoteConfigParameters(parameters: rawParameters); - _parameters = fetchedParameters; - notifyListeners(); - return newConfig; - } - - /// Sets the default config. - /// - /// Default config parameters should be set then when changes are needed the - /// parameters should be updated in the Firebase console. - Future setDefaults(Map defaults) async { - assert(defaults != null); - // Make defaults available even if fetch fails. - defaults.forEach((String key, dynamic value) { - if (!_parameters.containsKey(key)) { - final RemoteConfigValue remoteConfigValue = RemoteConfigValue._( - const Utf8Codec().encode(value.toString()), - ValueSource.valueDefault, - ); - _parameters[key] = remoteConfigValue; - } - }); - await channel.invokeMethod( - 'RemoteConfig#setDefaults', {'defaults': defaults}); - } - - /// Gets the value corresponding to the [key] as a String. - /// - /// If there is no parameter with corresponding [key] then the default - /// String value is returned. - String getString(String key) { - if (_parameters.containsKey(key)) { - return _parameters[key].asString(); - } else { - return defaultValueForString; - } - } - - /// Gets the value corresponding to the [key] as an int. - /// - /// If there is no parameter with corresponding [key] then the default - /// int value is returned. - int getInt(String key) { - if (_parameters.containsKey(key)) { - return _parameters[key].asInt(); - } else { - return defaultValueForInt; - } - } - - /// Gets the value corresponding to the [key] as a double. - /// - /// If there is no parameter with corresponding [key] then the default double - /// value is returned. - double getDouble(String key) { - if (_parameters.containsKey(key)) { - return _parameters[key].asDouble(); - } else { - return defaultValueForDouble; - } - } - - /// Gets the value corresponding to the [key] as a bool. - /// - /// If there is no parameter with corresponding [key] then the default bool - /// value is returned. - bool getBool(String key) { - if (_parameters.containsKey(key)) { - return _parameters[key].asBool(); - } else { - return defaultValueForBool; - } - } - - /// Gets the [RemoteConfigValue] corresponding to the [key]. - /// - /// If there is no parameter with corresponding key then a [RemoteConfigValue] - /// with a null value and static source is returned. - RemoteConfigValue getValue(String key) { - if (_parameters.containsKey(key)) { - return _parameters[key]; - } else { - return RemoteConfigValue._(null, ValueSource.valueStatic); - } - } - - /// Gets all [RemoteConfigValue]. - /// - /// This includes all remote and default values - Map getAll() { - return Map.unmodifiable(_parameters); - } -} diff --git a/packages/firebase_remote_config/lib/src/remote_config_fetch_throttled_exception.dart b/packages/firebase_remote_config/lib/src/remote_config_fetch_throttled_exception.dart deleted file mode 100644 index 7e8f28cd1eba..000000000000 --- a/packages/firebase_remote_config/lib/src/remote_config_fetch_throttled_exception.dart +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -part of firebase_remote_config; - -/// Exception thrown when the fetch() operation cannot be completed successfully, due to throttling. -class FetchThrottledException implements Exception { - FetchThrottledException._({int endTimeInMills}) { - _throttleEnd = DateTime.fromMillisecondsSinceEpoch(endTimeInMills); - } - - DateTime _throttleEnd; - - DateTime get throttleEnd => _throttleEnd; - - @override - String toString() { - final Duration duration = _throttleEnd.difference(DateTime.now()); - return '''FetchThrottledException -Fetching throttled, try again in ${duration.inMilliseconds} milliseconds'''; - } -} diff --git a/packages/firebase_remote_config/lib/src/remote_config_last_fetch_status.dart b/packages/firebase_remote_config/lib/src/remote_config_last_fetch_status.dart deleted file mode 100644 index ad82f9a3d876..000000000000 --- a/packages/firebase_remote_config/lib/src/remote_config_last_fetch_status.dart +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -part of firebase_remote_config; - -/// LastFetchStatus defines the possible status values of the last fetch. -enum LastFetchStatus { success, failure, throttled, noFetchYet } diff --git a/packages/firebase_remote_config/lib/src/remote_config_settings.dart b/packages/firebase_remote_config/lib/src/remote_config_settings.dart deleted file mode 100644 index b6c4b930788f..000000000000 --- a/packages/firebase_remote_config/lib/src/remote_config_settings.dart +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -part of firebase_remote_config; - -/// RemoteConfigSettings can be used to configure how Remote Config operates. -class RemoteConfigSettings { - RemoteConfigSettings( - {this.minimumFetchIntervalMillis = 43200000, - this.fetchTimeoutMillis = 60000}); - - /// Set the minimum fetch interval for Remote Config, in milliseconds - /// - /// Indicates the default value in milliseconds to set for the minimum - /// interval that needs to elapse before a fetch request can again be made - /// to the Remote Config server. Defaults to 43200000 (Twelve hours). - final int minimumFetchIntervalMillis; - - /// Set the fetch timeout for Remote Config, in milliseconds - /// - /// Indicates the default value in milliseconds to abandon a pending fetch - /// request made to the Remote Config server. Defaults to 60000 (One minute). - final int fetchTimeoutMillis; -} diff --git a/packages/firebase_remote_config/macos/Classes/FirebaseRemoteConfigPlugin.h b/packages/firebase_remote_config/macos/Classes/FirebaseRemoteConfigPlugin.h deleted file mode 120000 index 4907dbb67f9b..000000000000 --- a/packages/firebase_remote_config/macos/Classes/FirebaseRemoteConfigPlugin.h +++ /dev/null @@ -1 +0,0 @@ -../../ios/Classes/FirebaseRemoteConfigPlugin.h \ No newline at end of file diff --git a/packages/firebase_remote_config/macos/Classes/FirebaseRemoteConfigPlugin.m b/packages/firebase_remote_config/macos/Classes/FirebaseRemoteConfigPlugin.m deleted file mode 120000 index fd21e0a84a0f..000000000000 --- a/packages/firebase_remote_config/macos/Classes/FirebaseRemoteConfigPlugin.m +++ /dev/null @@ -1 +0,0 @@ -../../ios/Classes/FirebaseRemoteConfigPlugin.m \ No newline at end of file diff --git a/packages/firebase_remote_config/test/firebase_remote_config_test.dart b/packages/firebase_remote_config/test/firebase_remote_config_test.dart deleted file mode 100644 index 138fd16870aa..000000000000 --- a/packages/firebase_remote_config/test/firebase_remote_config_test.dart +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:firebase_remote_config/firebase_remote_config.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - final int lastFetchTime = 1520618753782; - Map getDefaultInstance() { - return { - 'lastFetchTime': lastFetchTime, - 'lastFetchStatus': 'success', - 'minimumFetchInterval': 0, // 12 hours is default, 0 seconds in test - 'fetchTimeout': 60, // 60 seconds is remote-config default - 'parameters': { - 'param1': { - 'source': 'static', - 'value': [118, 97, 108, 49], // UTF-8 encoded 'val1' - }, - }, - }; - } - - group('$RemoteConfig', () { - final List log = []; - - setUp(() async { - RemoteConfig.channel - .setMockMethodCallHandler((MethodCall methodCall) async { - log.add(methodCall); - switch (methodCall.method) { - case 'RemoteConfig#instance': - return getDefaultInstance(); - default: - return true; - } - }); - }); - - test('instance', () async { - final RemoteConfig remoteConfig = await RemoteConfig.instance; - expect( - log, - [ - isMethodCall('RemoteConfig#instance', arguments: null), - ], - ); - expect(remoteConfig.remoteConfigSettings.minimumFetchIntervalMillis, 0); - expect(remoteConfig.remoteConfigSettings.fetchTimeoutMillis, 60 * 1000); - expect(remoteConfig.lastFetchTime, - DateTime.fromMillisecondsSinceEpoch(lastFetchTime)); - expect(remoteConfig.lastFetchStatus, LastFetchStatus.values[0]); - }); - - test('doubleInstance', () async { - final List> futures = >[ - RemoteConfig.instance, - RemoteConfig.instance, - ]; - Future.wait(futures).then((List remoteConfigs) { - // Check that both returned Remote Config instances are the same. - expect(remoteConfigs[0], remoteConfigs[1]); - }); - }); - }); - - group('$RemoteConfig', () { - final List log = []; - - final int lastFetchTime = 1520618753782; - RemoteConfig remoteConfig; - - setUp(() async { - RemoteConfig.channel - .setMockMethodCallHandler((MethodCall methodCall) async { - log.add(methodCall); - switch (methodCall.method) { - case 'RemoteConfig#setDefaults': - return null; - case 'RemoteConfig#fetch': - return { - 'lastFetchTime': lastFetchTime, - 'lastFetchStatus': 'success', - }; - case 'RemoteConfig#instance': - return getDefaultInstance(); - case 'RemoteConfig#activate': - return { - 'parameters': { - 'param1': { - 'source': 'remote', - 'value': [118, 97, 108, 49], // UTF-8 encoded 'val1' - }, - 'param2': { - 'source': 'remote', - 'value': [49, 50, 51, 52, 53], // UTF-8 encoded '12345' - }, - 'param3': { - 'source': 'default', - 'value': [51, 46, 49, 52], // UTF-8 encoded '3.14' - }, - 'param4': { - 'source': 'remote', - 'value': [116, 114, 117, 101], // UTF-8 encoded 'true' - }, - 'param5': { - 'source': 'default', - 'value': [ - 102, - 97, - 108, - 115, - 101 - ], // UTF-8 encoded 'false' - }, - 'param6': {'source': 'default', 'value': null} - }, - 'newConfig': true, - }; - case 'RemoteConfig#setConfigSettings': - return null; - default: - return true; - } - }); - remoteConfig = await RemoteConfig.instance; - log.clear(); - }); - - test('setDefaults', () async { - await remoteConfig.setDefaults({ - 'foo': 'bar', - }); - expect(log, [ - isMethodCall( - 'RemoteConfig#setDefaults', - arguments: { - 'defaults': { - 'foo': 'bar', - }, - }, - ), - ]); - }); - - test('fetch', () async { - await remoteConfig.fetch(expiration: const Duration(hours: 1)); - expect( - log, - [ - isMethodCall( - 'RemoteConfig#fetch', - arguments: { - 'expiration': 3600, - }, - ), - ], - ); - }); - - test('activate', () async { - final bool newConfig = await remoteConfig.activateFetched(); - expect( - log, - [ - isMethodCall( - 'RemoteConfig#activate', - arguments: null, - ), - ], - ); - expect(newConfig, true); - expect(remoteConfig.getString('param1'), 'val1'); - expect(remoteConfig.getInt('param2'), 12345); - expect(remoteConfig.getDouble('param3'), 3.14); - expect(remoteConfig.getBool('param4'), true); - expect(remoteConfig.getBool('param5'), false); - expect(remoteConfig.getInt('param6'), 0); - - remoteConfig.getAll().forEach((String key, RemoteConfigValue value) { - switch (key) { - case 'param1': - expect(value.asString(), 'val1'); - break; - case 'param2': - expect(value.asInt(), 12345); - break; - case 'param3': - expect(value.asDouble(), 3.14); - break; - case 'param4': - expect(value.asBool(), true); - break; - case 'param5': - expect(value.asBool(), false); - break; - case 'param6': - expect(value.asInt(), 0); - break; - default: - } - }); - - final Map resultAllSources = remoteConfig - .getAll() - .map((String key, RemoteConfigValue value) => - MapEntry(key, value.source)); - expect(resultAllSources, { - 'param1': ValueSource.valueRemote, - 'param2': ValueSource.valueRemote, - 'param3': ValueSource.valueDefault, - 'param4': ValueSource.valueRemote, - 'param5': ValueSource.valueDefault, - 'param6': ValueSource.valueDefault, - }); - }); - - test('setConfigSettings', () async { - var intervalSecs = 100; - expect(remoteConfig.remoteConfigSettings.minimumFetchIntervalMillis, 0); - final RemoteConfigSettings remoteConfigSettings = - // milliseconds in the Dart API (to match firebase-js-sdk) - RemoteConfigSettings(minimumFetchIntervalMillis: intervalSecs * 1000); - await remoteConfig.setConfigSettings(remoteConfigSettings); - expect( - log, - [ - isMethodCall( - 'RemoteConfig#setConfigSettings', - arguments: { - // milliseconds in Dart API, but just seconds for native ios/android - 'minimumFetchInterval': intervalSecs, - 'fetchTimeout': 60, // from our mock instance above - }, - ), - ], - ); - expect(remoteConfig.remoteConfigSettings.minimumFetchIntervalMillis, - intervalSecs * 1000); - }); - }); -}