blob: be3912c61dbed34fa3cabcd0505842aa10b0eaad [file] [log] [blame]
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<String, RemoteConfigValue> _parameters;
DateTime _lastFetchTime;
LastFetchStatus _lastFetchStatus;
RemoteConfigSettings _remoteConfigSettings;
DateTime get lastFetchTime => _lastFetchTime;
LastFetchStatus get lastFetchStatus => _lastFetchStatus;
RemoteConfigSettings get remoteConfigSettings => _remoteConfigSettings;
static Completer<RemoteConfig> _instanceCompleter = Completer<RemoteConfig>();
/// Gets the instance of RemoteConfig for the default Firebase app.
static Future<RemoteConfig> get instance async {
if (!_instanceCompleter.isCompleted) {
_getRemoteConfigInstance();
}
return _instanceCompleter.future;
}
static void _getRemoteConfigInstance() async {
final Map<dynamic, dynamic> properties =
// TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter.
// https://github.com/flutter/flutter/issues/26431
// ignore: strong_mode_implicit_dynamic_method
await channel.invokeMethod('RemoteConfig#instance');
final RemoteConfig instance = RemoteConfig();
instance._lastFetchTime =
DateTime.fromMillisecondsSinceEpoch(properties['lastFetchTime']);
instance._lastFetchStatus =
_parseLastFetchStatus(properties['lastFetchStatus']);
final RemoteConfigSettings remoteConfigSettings =
RemoteConfigSettings(debugMode: properties['inDebugMode']);
instance._remoteConfigSettings = remoteConfigSettings;
instance._parameters =
_parseRemoteConfigParameters(parameters: properties['parameters']);
_instanceCompleter.complete(instance);
}
static Map<String, RemoteConfigValue> _parseRemoteConfigParameters(
{Map<dynamic, dynamic> parameters}) {
final Map<String, RemoteConfigValue> parsedParameters =
<String, RemoteConfigValue>{};
parameters.forEach((dynamic key, dynamic value) {
final ValueSource valueSource = _parseValueSource(value['source']);
final RemoteConfigValue remoteConfigValue =
RemoteConfigValue._(value['value'].cast<int>(), valueSource);
parsedParameters[key] = remoteConfigValue;
});
return parsedParameters;
}
static ValueSource _parseValueSource(String sourceStr) {
switch (sourceStr) {
case 'valueStatic':
return ValueSource.valueStatic;
case 'valueDefault':
return ValueSource.valueDefault;
case 'valueRemote':
return ValueSource.valueRemote;
default:
return ValueSource.valueStatic;
}
}
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 for enabling developer mode.
Future<void> setConfigSettings(
RemoteConfigSettings remoteConfigSettings) async {
await channel
// TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter.
// https://github.com/flutter/flutter/issues/26431
// ignore: strong_mode_implicit_dynamic_method
.invokeMethod('RemoteConfig#setConfigSettings', <String, dynamic>{
'debugMode': remoteConfigSettings.debugMode,
});
_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<void> fetch({Duration expiration = const Duration(hours: 12)}) async {
try {
// TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter.
// https://github.com/flutter/flutter/issues/26431
// ignore: strong_mode_implicit_dynamic_method
final Map<dynamic, dynamic> properties = await channel.invokeMethod(
'RemoteConfig#fetch',
<dynamic, dynamic>{'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('Unable to fetch remote config');
}
}
}
/// 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<bool> activateFetched() async {
final Map<dynamic, dynamic> properties =
// TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter.
// https://github.com/flutter/flutter/issues/26431
// ignore: strong_mode_implicit_dynamic_method
await channel.invokeMethod('RemoteConfig#activate');
final Map<dynamic, dynamic> rawParameters = properties['parameters'];
final bool newConfig = properties['newConfig'];
final Map<String, RemoteConfigValue> 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<void> setDefaults(Map<String, dynamic> defaults) async {
// Make defaults available even if fetch fails.
defaults.forEach((String key, dynamic value) {
if (!_parameters.containsKey(key)) {
final ValueSource valueSource = ValueSource.valueDefault;
final RemoteConfigValue remoteConfigValue = RemoteConfigValue._(
const Utf8Codec().encode(value.toString()),
valueSource,
);
_parameters[key] = remoteConfigValue;
}
});
// TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter.
// https://github.com/flutter/flutter/issues/26431
// ignore: strong_mode_implicit_dynamic_method
await channel.invokeMethod(
'RemoteConfig#setDefaults', <String, dynamic>{'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);
}
}
}