import 'dart:convert'; import 'dart:io'; import 'package:firebase_remote_config/firebase_remote_config.dart'; import 'package:guru_app/ads/core/ads_config.dart'; import 'package:guru_app/analytics/data/analytics_model.dart'; import 'package:guru_app/firebase/firebase.dart'; import 'package:guru_app/firebase/remoteconfig/reserved_remote_config_models.dart'; import 'package:guru_app/guru_app.dart'; import 'package:guru_utils/http/http_model.dart'; import 'package:guru_utils/log/log.dart'; import 'package:rxdart/rxdart.dart'; import 'package:guru_utils/remote/remote_config.dart'; part "remote_config_interface.dart"; /// Created by Haoyi on 2020/4/22 /// part "remote_config_reserved_constants.dart"; class RemoteConfigManager extends IRemoteConfig { final BehaviorSubject _subject = BehaviorSubject.seeded(null); static RemoteConfigManager? _instance; static RemoteConfigManager _getInstance() { _instance ??= RemoteConfigManager._internal(); return _instance!; } factory RemoteConfigManager() => _getInstance(); static RemoteConfigManager get instance => _getInstance(); static final RegExp invalidABKeyRegExp = RegExp('[^a-zA-Z0-9_-]'); RemoteConfigManager._internal(); Future init(Map defaultConfigs) async { final remoteConfig = FirebaseRemoteConfig.instance; await remoteConfig.setConfigSettings(RemoteConfigSettings( fetchTimeout: const Duration(seconds: 15), minimumFetchInterval: const Duration(hours: 2), )); _subject.add(FirebaseRemoteConfigWrapper._(remoteConfig)); try { await remoteConfig.setDefaults(defaultConfigs); await remoteConfig.activate(); } catch (exception) { Log.d("Unable to fetch remote config. Cached or default values will be used!", error: exception); } finally { _subject.add(FirebaseRemoteConfigWrapper._(remoteConfig)); } } Future fetchAndActivate() async { final remoteConfig = FirebaseRemoteConfig.instance; try { await remoteConfig.fetchAndActivate(); } catch (exception) { Log.d("Unable to fetch remote config. Cached or default values will be used!", error: exception); } finally { _subject.add(FirebaseRemoteConfigWrapper._(remoteConfig)); } } Future dumpString() async { final config = FirebaseRemoteConfig.instance; final data = config.getAll(); String result = ""; for (var entry in data.entries) { result += "[${entry.key}] ==> (${entry.value.asString()})\n"; } return result; } static String valueSourceToString(ValueSource source) { switch (source) { case ValueSource.valueRemote: return "Remote"; case ValueSource.valueStatic: return "Static"; default: return "Default"; } } Map allData() { final config = FirebaseRemoteConfig.instance; final data = config.getAll(); final result = { for (var entry in data.entries) "${entry.key} [${valueSourceToString(entry.value.source)}]": entry.value.asString() }; result["last_fetch_remote_config_time"] = config.lastFetchTime.toString(); result["last_fetch_remote_config_status"] = config.lastFetchStatus.toString(); return result; } Future forceFetch({bool debug = false}) async { final remoteConfig = FirebaseRemoteConfig.instance; try { await remoteConfig.setConfigSettings(RemoteConfigSettings( fetchTimeout: const Duration(seconds: 15), minimumFetchInterval: const Duration(seconds: 0), )); await remoteConfig.fetchAndActivate(); } catch (exception) { Log.d("Unable to fetch remote config. Cached or default values will be used $exception", error: exception); if (debug) { rethrow; } } finally { _subject.add(FirebaseRemoteConfigWrapper._(remoteConfig)); await remoteConfig.setConfigSettings(RemoteConfigSettings( fetchTimeout: const Duration(seconds: 15), minimumFetchInterval: const Duration(hours: 2), )); } } Map getABProperties() { final config = FirebaseRemoteConfig.instance; final data = config.getAll(); final result = {}; final invalidABKeys = {}; dynamic cause; for (var entry in data.entries) { final valueStr = entry.value.asString(); if (valueStr.contains("guru_ab_")) { try { final jsonValue = json.decode(valueStr); if (jsonValue is Map) { for (var jsonEntry in jsonValue.entries) { if (jsonEntry.key.contains("guru_ab_")) { String abName = jsonEntry.key.replaceFirst("guru_ab_", ""); if (abName.contains(invalidABKeyRegExp)) { Log.w("abName($abName) length is invalid! $abName"); invalidABKeys.add(abName); } else { if (abName.length > 20) { invalidABKeys.add(abName); abName = abName.substring(0, 20); } result[GuruAnalytics.buildVariantKey(abName)] = jsonEntry.value.toString(); Log.i("abName:ab_$abName value:${jsonEntry.value}"); } } } } } catch (error, stacktrace) { Log.w("decode json error! $error"); cause = error; } } } if (invalidABKeys.isNotEmpty) { GuruAnalytics.instance .logException(InvalidABPropertyKeysException(invalidABKeys, cause: cause)); } return result; } @override bool? getBool(String name, {bool? defaultValue}) => _subject.value?.getBool(name) ?? defaultValue; @override String? getString(String name, {String? defaultValue}) => _subject.value?.getString(name) ?? defaultValue ?? RemoteConfigReservedConstants.getDefaultConfigString(name); @override double? getDouble(String name, {double? defaultValue}) => _subject.value?.getDouble(name) ?? defaultValue; @override int? getInt(String name, {int? defaultValue}) => _subject.value?.getInt(name) ?? defaultValue; Stream observeConfig() => _subject.stream .map((config) => config ?? FirebaseRemoteConfigWrapper._(FirebaseRemoteConfig.instance)); @override Stream observeBool(String name, {bool? defaultValue}) => observeConfig().map((config) => config.getBool(name)); @override Stream observeString(String name, {String? defaultValue}) => observeConfig().map((config) => config.getString(name)); @override Stream observeDouble(String name, {double? defaultValue}) => observeConfig().map((config) => config.getDouble(name)); @override Stream observeInt(String name, {int? defaultValue}) => observeConfig().map((config) => config.getInt(name)); } class FirebaseRemoteConfigWrapper { final FirebaseRemoteConfig config; const FirebaseRemoteConfigWrapper._(this.config); DateTime get lastFetchTime => config.lastFetchTime; RemoteConfigFetchStatus get lastFetchStatus => config.lastFetchStatus; static String _key(String key) => GuruApp.instance.getRemoteConfigKey(key); String getString(String key) => config.getString(_key(key)); bool getBool(String key) => config.getBool(_key(key)); double getDouble(String key) => config.getDouble(_key(key)); int getInt(String key) => config.getInt(_key(key)); }