Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@
## Observability

- firebase_core (Firebase initialization)
- firebase_crashlytics (opt-in crash reporting)
- firebase_crashlytics (crash reporting, enabled by default)
- firebase_performance (performance monitoring, enabled by default)

## Internationalization

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ class CrashlyticsPreferenceStorage {

bool read() {
try {
return _prefs.getBool(_key) ?? false;
return _prefs.getBool(_key) ?? true;
} catch (error, stack) {
debugPrint('Failed to read crashlytics preference: $error\n$stack');
return false;
return true;
}
Comment thread
using-system marked this conversation as resolved.
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';

class PerformancePreferenceStorage {
PerformancePreferenceStorage(this._prefs);

static const _key = 'observability_performance_enabled';

final SharedPreferences _prefs;

bool read() {
try {
return _prefs.getBool(_key) ?? true;
} catch (error, stack) {
debugPrint('Failed to read performance preference: $error\n$stack');
return true;
}
}

Future<void> write(bool enabled) async {
final didWrite = await _prefs.setBool(_key, enabled);
if (!didWrite) {
throw Exception('Failed to persist performance preference.');
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class CrashlyticsToggleTile extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context);
final crashlyticsAsync = ref.watch(crashlyticsPreferenceProvider);
final enabled = crashlyticsAsync.valueOrNull ?? false;
final enabled = crashlyticsAsync.valueOrNull ?? true;

return SwitchListTile(
secondary: const Icon(Icons.bug_report_outlined),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';

import 'crashlytics_toggle_tile.dart';
import 'performance_toggle_tile.dart';

class ObservabilitySection extends StatelessWidget {
const ObservabilitySection({super.key});
Expand All @@ -12,6 +13,8 @@ class ObservabilitySection extends StatelessWidget {
children: [
CrashlyticsToggleTile(),
Divider(height: 1),
PerformanceToggleTile(),
Divider(height: 1),
],
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import 'package:cookmate/l10n/app_localizations.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../providers.dart';

class PerformanceToggleTile extends ConsumerWidget {
const PerformanceToggleTile({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context);
final performanceAsync = ref.watch(performancePreferenceProvider);
final enabled = performanceAsync.valueOrNull ?? true;

return SwitchListTile(
secondary: const Icon(Icons.speed),
title: Text(l10n.settingsPerformanceTitle),
subtitle: Text(l10n.settingsPerformanceDescription),
value: enabled,
onChanged: performanceAsync.isLoading ? null : (value) async {
try {
await ref
.read(performancePreferenceProvider.notifier)
.setPreference(value);
} catch (error, stack) {
debugPrint('Failed to change performance: $error\n$stack');
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text(l10n.settingsPerformanceChangeFailureSnackbar),
),
);
}
}
},
);
}
}
40 changes: 40 additions & 0 deletions lib/features/observability/providers.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:firebase_performance/firebase_performance.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../core/shared_preferences_provider.dart';
import 'data/crashlytics_preference_storage.dart';
import 'data/performance_preference_storage.dart';

final crashlyticsPreferenceStorageProvider =
FutureProvider<CrashlyticsPreferenceStorage>((ref) async {
Expand Down Expand Up @@ -42,3 +44,41 @@ final crashlyticsPreferenceProvider =
AsyncNotifierProvider<CrashlyticsPreferenceNotifier, bool>(
CrashlyticsPreferenceNotifier.new,
);

final performancePreferenceStorageProvider =
FutureProvider<PerformancePreferenceStorage>((ref) async {
final prefs = await ref.watch(sharedPreferencesProvider.future);
return PerformancePreferenceStorage(prefs);
});

class PerformancePreferenceNotifier extends AsyncNotifier<bool> {
@override
Future<bool> build() async {
final storage =
await ref.watch(performancePreferenceStorageProvider.future);
return storage.read();
}

Future<void> setPreference(bool enabled) async {
final storage =
await ref.read(performancePreferenceStorageProvider.future);
state = const AsyncValue<bool>.loading().copyWithPrevious(state);
try {
await storage.write(enabled);
if (Firebase.apps.isNotEmpty) {
await FirebasePerformance.instance
.setPerformanceCollectionEnabled(enabled);
}
state = AsyncValue.data(enabled);
} catch (error, stack) {
state =
AsyncValue<bool>.error(error, stack).copyWithPrevious(state);
rethrow;
}
}
}

final performancePreferenceProvider =
AsyncNotifierProvider<PerformancePreferenceNotifier, bool>(
PerformancePreferenceNotifier.new,
);
6 changes: 5 additions & 1 deletion lib/l10n/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -147,5 +147,9 @@
"settingsSectionObservability": "Beobachtbarkeit",
"settingsCrashlyticsTitle": "Absturzbericht",
"settingsCrashlyticsDescription": "Anonyme Absturzberichte senden, um die App zu verbessern",
"settingsCrashlyticsChangeFailureSnackbar": "Absturzbericht-Einstellung konnte nicht ge盲ndert werden. Bitte versuchen Sie es erneut."
"settingsCrashlyticsChangeFailureSnackbar": "Absturzbericht-Einstellung konnte nicht ge盲ndert werden. Bitte versuchen Sie es erneut.",

"settingsPerformanceTitle": "Leistungs眉berwachung",
"settingsPerformanceDescription": "Anonyme Leistungsdaten sammeln, um die App zu verbessern",
"settingsPerformanceChangeFailureSnackbar": "Leistungs眉berwachung-Einstellung konnte nicht ge盲ndert werden. Bitte versuchen Sie es erneut."
}
11 changes: 10 additions & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -452,5 +452,14 @@
"@settingsCrashlyticsDescription": { "description": "Subtitle explaining what crash reporting does." },

"settingsCrashlyticsChangeFailureSnackbar": "Couldn't change crash reporting setting. Please try again.",
"@settingsCrashlyticsChangeFailureSnackbar": { "description": "Shown when persisting the crash reporting toggle fails." }
"@settingsCrashlyticsChangeFailureSnackbar": { "description": "Shown when persisting the crash reporting toggle fails." },

"settingsPerformanceTitle": "Performance monitoring",
"@settingsPerformanceTitle": { "description": "Title of the performance monitoring toggle tile." },

"settingsPerformanceDescription": "Collect anonymous performance data to help improve the app",
"@settingsPerformanceDescription": { "description": "Subtitle explaining what performance monitoring does." },

"settingsPerformanceChangeFailureSnackbar": "Couldn't change performance monitoring setting. Please try again.",
"@settingsPerformanceChangeFailureSnackbar": { "description": "Shown when persisting the performance monitoring toggle fails." }
}
6 changes: 5 additions & 1 deletion lib/l10n/app_es.arb
Original file line number Diff line number Diff line change
Expand Up @@ -147,5 +147,9 @@
"settingsSectionObservability": "Observabilidad",
"settingsCrashlyticsTitle": "Informe de fallos",
"settingsCrashlyticsDescription": "Enviar informes de fallos an贸nimos para mejorar la aplicaci贸n",
"settingsCrashlyticsChangeFailureSnackbar": "No se pudo cambiar la configuraci贸n de informe de fallos. Int茅ntelo de nuevo."
"settingsCrashlyticsChangeFailureSnackbar": "No se pudo cambiar la configuraci贸n de informe de fallos. Int茅ntelo de nuevo.",

"settingsPerformanceTitle": "Monitoreo de rendimiento",
"settingsPerformanceDescription": "Recopilar datos de rendimiento an贸nimos para mejorar la aplicaci贸n",
"settingsPerformanceChangeFailureSnackbar": "No se pudo cambiar la configuraci贸n de monitoreo de rendimiento. Int茅ntelo de nuevo."
}
6 changes: 5 additions & 1 deletion lib/l10n/app_fr.arb
Original file line number Diff line number Diff line change
Expand Up @@ -147,5 +147,9 @@
"settingsSectionObservability": "Observabilit茅",
"settingsCrashlyticsTitle": "Rapport de plantage",
"settingsCrashlyticsDescription": "Envoyer des rapports de plantage anonymes pour am茅liorer l'application",
"settingsCrashlyticsChangeFailureSnackbar": "Impossible de modifier le param猫tre de rapport de plantage. Veuillez r茅essayer."
"settingsCrashlyticsChangeFailureSnackbar": "Impossible de modifier le param猫tre de rapport de plantage. Veuillez r茅essayer.",

"settingsPerformanceTitle": "Rapport de performance",
"settingsPerformanceDescription": "Collecter des donn茅es de performance anonymes pour am茅liorer l'application",
"settingsPerformanceChangeFailureSnackbar": "Impossible de modifier le param猫tre de performance. Veuillez r茅essayer."
}
11 changes: 11 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:firebase_performance/firebase_performance.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gemma/flutter_gemma.dart';
Expand All @@ -10,6 +11,7 @@ import 'package:shared_preferences/shared_preferences.dart';

import 'app.dart';
import 'features/observability/data/crashlytics_preference_storage.dart';
import 'features/observability/data/performance_preference_storage.dart';

void main() {
runZonedGuarded(
Expand All @@ -31,6 +33,15 @@ void main() {

FlutterError.onError =
FirebaseCrashlytics.instance.recordFlutterFatalError;

try {
final performanceEnabled =
PerformancePreferenceStorage(prefs).read();
await FirebasePerformance.instance
.setPerformanceCollectionEnabled(performanceEnabled);
} catch (e, stack) {
debugPrint('Firebase Performance init skipped: $e\n$stack');
}
} catch (e, stack) {
debugPrint('Firebase init skipped: $e\n$stack');
}
Expand Down
24 changes: 24 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.8.10"
firebase_performance:
dependency: "direct main"
description:
name: firebase_performance
sha256: b6948a02cb06d5da36e9f91f4a96b39745ab7565942a8134b7692ea6fc4ac595
url: "https://pub.dev"
source: hosted
version: "0.10.1+10"
firebase_performance_platform_interface:
dependency: transitive
description:
name: firebase_performance_platform_interface
sha256: "836b219dc99c59ee5b854ba4f6432781f40ae75b31ea112f3b8b2ffce8a01cd4"
url: "https://pub.dev"
source: hosted
version: "0.1.5+10"
firebase_performance_web:
dependency: transitive
description:
name: firebase_performance_web
sha256: "7b8cdc99f8c7ceafd6c3f51a3a661aef96bc9c5948af78d35bc9660941ed5b2f"
url: "https://pub.dev"
source: hosted
version: "0.1.7+16"
fixnum:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ dependencies:
http: ^1.4.0
firebase_core: ^3.13.0
firebase_crashlytics: ^4.3.5
firebase_performance: ^0.10.0+12

dev_dependencies:
flutter_test:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ void main() {
storage = CrashlyticsPreferenceStorage(prefs);
});

test('read returns false when nothing is stored', () {
expect(storage.read(), false);
test('read returns true when nothing is stored', () {
expect(storage.read(), true);
});

test('write then read returns the written value', () async {
Expand All @@ -28,12 +28,12 @@ void main() {
expect(storage.read(), false);
});

test('read returns false when stored value is corrupted', () async {
test('read returns true when stored value is corrupted', () async {
SharedPreferences.setMockInitialValues(<String, Object>{
'observability_crashlytics_enabled': 'not_a_bool',
});
final prefs = await SharedPreferences.getInstance();
final s = CrashlyticsPreferenceStorage(prefs);
expect(s.read(), false);
expect(s.read(), true);
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'package:cookmate/features/observability/data/performance_preference_storage.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
TestWidgetsFlutterBinding.ensureInitialized();

late PerformancePreferenceStorage storage;

setUp(() async {
SharedPreferences.setMockInitialValues(<String, Object>{});
final prefs = await SharedPreferences.getInstance();
storage = PerformancePreferenceStorage(prefs);
});

test('read returns true when nothing is stored', () {
expect(storage.read(), true);
});

test('write then read returns the written value', () async {
await storage.write(false);
expect(storage.read(), false);
});

test('write overwrites a previous value', () async {
await storage.write(false);
await storage.write(true);
expect(storage.read(), true);
});

test('read returns true when stored value is corrupted', () async {
SharedPreferences.setMockInitialValues(<String, Object>{
'observability_performance_enabled': 'not_a_bool',
});
final prefs = await SharedPreferences.getInstance();
final s = PerformancePreferenceStorage(prefs);
expect(s.read(), true);
});
}
Loading