m3_lightmeter/lib/data/remote_config_service.dart
Vadim fc37016770
ML-154 Improve Crashlytics reports (#155)
* removed unused analytics event & added `logCrash`

* added analytics to `RemoteConfigService`

* run app with `runZonedGuarded`

* added crash logging to `CameraContainerBloc`

* log product id for IAP errors

* typo

* log crashes in `RemoteConfigService`

* ignore silent `FlutterError`

* fixed `evFromImage` test

* fixed `showBuyProDialog` test

* log errors in console

* depend on iap 0.7.2
2024-01-27 23:20:53 +01:00

137 lines
4.2 KiB
Dart

import 'dart:async';
import 'dart:developer';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:flutter/foundation.dart';
import 'package:lightmeter/data/analytics/analytics.dart';
import 'package:lightmeter/data/models/feature.dart';
abstract class IRemoteConfigService {
const IRemoteConfigService();
Future<void> activeAndFetchFeatures();
Future<void> fetchConfig();
dynamic getValue(Feature feature);
Map<Feature, dynamic> getAll();
Stream<Set<Feature>> onConfigUpdated();
bool isEnabled(Feature feature);
}
class RemoteConfigService implements IRemoteConfigService {
final LightmeterAnalytics analytics;
const RemoteConfigService(this.analytics);
@override
Future<void> activeAndFetchFeatures() async {
final FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.instance;
const cacheStaleDuration = kDebugMode ? Duration(minutes: 1) : Duration(hours: 12);
try {
await remoteConfig.setConfigSettings(
RemoteConfigSettings(
fetchTimeout: const Duration(seconds: 15),
minimumFetchInterval: cacheStaleDuration,
),
);
await remoteConfig.setDefaults(featuresDefaultValues.map((key, value) => MapEntry(key.name, value)));
await remoteConfig.activate();
await remoteConfig.ensureInitialized();
log('Firebase remote config initialized successfully');
} on FirebaseException catch (e) {
_logError('Firebase exception during Firebase Remote Config initialization: $e');
} catch (e) {
_logError('Error during Firebase Remote Config initialization: $e');
}
}
@override
Future<void> fetchConfig() async {
try {
// https://github.com/firebase/flutterfire/issues/6196#issuecomment-927751667
await Future.delayed(const Duration(seconds: 1));
await FirebaseRemoteConfig.instance.fetch();
} on FirebaseException catch (e) {
_logError('Firebase exception during Firebase Remote Config fetch: $e');
} catch (e) {
_logError('Error during Firebase Remote Config fetch: $e');
}
}
@override
dynamic getValue(Feature feature) => FirebaseRemoteConfig.instance.getValue(feature.name).toValue(feature);
@override
Map<Feature, dynamic> getAll() {
final Map<Feature, dynamic> result = {};
for (final value in FirebaseRemoteConfig.instance.getAll().entries) {
try {
final feature = Feature.values.firstWhere((f) => f.name == value.key);
result[feature] = value.value.toValue(feature);
} catch (e, stackTrace) {
_logError(e, stackTrace: stackTrace);
}
}
return result;
}
@override
Stream<Set<Feature>> onConfigUpdated() => FirebaseRemoteConfig.instance.onConfigUpdated.asyncMap(
(event) async {
await FirebaseRemoteConfig.instance.activate();
final Set<Feature> updatedFeatures = {};
for (final key in event.updatedKeys) {
try {
updatedFeatures.add(Feature.values.firstWhere((element) => element.name == key));
} catch (e, stackTrace) {
_logError(e, stackTrace: stackTrace);
}
}
return updatedFeatures;
},
);
@override
bool isEnabled(Feature feature) => FirebaseRemoteConfig.instance.getBool(feature.name);
void _logError(dynamic throwable, {StackTrace? stackTrace}) => analytics.logCrash(throwable, stackTrace);
}
class MockRemoteConfigService implements IRemoteConfigService {
const MockRemoteConfigService();
@override
Future<void> activeAndFetchFeatures() async {}
@override
Future<void> fetchConfig() async {}
@override
Map<Feature, dynamic> getAll() => featuresDefaultValues;
@override
dynamic getValue(Feature feature) => featuresDefaultValues[feature];
@override
// ignore: cast_nullable_to_non_nullable
bool isEnabled(Feature feature) => featuresDefaultValues[feature] as bool;
@override
Stream<Set<Feature>> onConfigUpdated() => const Stream.empty();
}
extension on RemoteConfigValue {
dynamic toValue(Feature feature) {
switch (feature) {
case Feature.showUnlockProOnMainScreen:
return asBool();
}
}
}