added firebase analytics
This commit is contained in:
parent
41e7d889b6
commit
4a5919a979
9 changed files with 87 additions and 25 deletions
|
@ -8,6 +8,8 @@ import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:aves/widgets/common/routes.dart';
|
import 'package:aves/widgets/common/routes.dart';
|
||||||
import 'package:aves/widgets/home_page.dart';
|
import 'package:aves/widgets/home_page.dart';
|
||||||
import 'package:aves/widgets/welcome_page.dart';
|
import 'package:aves/widgets/welcome_page.dart';
|
||||||
|
import 'package:firebase_analytics/firebase_analytics.dart';
|
||||||
|
import 'package:firebase_analytics/observer.dart';
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
@ -41,7 +43,9 @@ class AvesApp extends StatefulWidget {
|
||||||
|
|
||||||
class _AvesAppState extends State<AvesApp> {
|
class _AvesAppState extends State<AvesApp> {
|
||||||
Future<void> _appSetup;
|
Future<void> _appSetup;
|
||||||
final NavigatorObserver _routeTracker = CrashlyticsRouteTracker();
|
// observers are not registered when using the same list object with different items
|
||||||
|
// the list itself needs to be reassigned
|
||||||
|
List<NavigatorObserver> _navigatorObservers = [];
|
||||||
final _newIntentChannel = EventChannel('deckers.thibault/aves/intent');
|
final _newIntentChannel = EventChannel('deckers.thibault/aves/intent');
|
||||||
final _navigatorKey = GlobalKey<NavigatorState>();
|
final _navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
@ -93,11 +97,12 @@ class _AvesAppState extends State<AvesApp> {
|
||||||
|
|
||||||
Future<void> _setup() async {
|
Future<void> _setup() async {
|
||||||
await Firebase.initializeApp().then((app) {
|
await Firebase.initializeApp().then((app) {
|
||||||
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;
|
final crashlytics = FirebaseCrashlytics.instance;
|
||||||
FirebaseCrashlytics.instance.setCustomKey('locales', window.locales.join(', '));
|
FlutterError.onError = crashlytics.recordFlutterError;
|
||||||
|
crashlytics.setCustomKey('locales', window.locales.join(', '));
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
FirebaseCrashlytics.instance.setCustomKey('timezone', '${now.timeZoneName} (${now.timeZoneOffset})');
|
crashlytics.setCustomKey('timezone', '${now.timeZoneName} (${now.timeZoneOffset})');
|
||||||
FirebaseCrashlytics.instance.setCustomKey(
|
crashlytics.setCustomKey(
|
||||||
'build_mode',
|
'build_mode',
|
||||||
kReleaseMode
|
kReleaseMode
|
||||||
? 'release'
|
? 'release'
|
||||||
|
@ -106,7 +111,11 @@ class _AvesAppState extends State<AvesApp> {
|
||||||
: 'debug');
|
: 'debug');
|
||||||
});
|
});
|
||||||
await settings.init();
|
await settings.init();
|
||||||
await settings.initCrashlytics();
|
await settings.initFirebase();
|
||||||
|
_navigatorObservers = [
|
||||||
|
FirebaseAnalyticsObserver(analytics: FirebaseAnalytics()),
|
||||||
|
CrashlyticsRouteTracker(),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onNewIntent(Map intentData) {
|
void _onNewIntent(Map intentData) {
|
||||||
|
@ -126,28 +135,20 @@ class _AvesAppState extends State<AvesApp> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// place the settings provider above `MaterialApp`
|
// place the settings provider above `MaterialApp`
|
||||||
// so it can be used during navigation transitions
|
// so it can be used during navigation transitions
|
||||||
final home = FutureBuilder<void>(
|
|
||||||
future: _appSetup,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (!snapshot.hasError && snapshot.connectionState == ConnectionState.done) {
|
|
||||||
return getFirstPage();
|
|
||||||
}
|
|
||||||
return Scaffold(
|
|
||||||
body: snapshot.hasError ? _buildError(snapshot.error) : SizedBox.shrink(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return SettingsProvider(
|
return SettingsProvider(
|
||||||
child: OverlaySupport(
|
child: OverlaySupport(
|
||||||
child: FutureBuilder<void>(
|
child: FutureBuilder<void>(
|
||||||
future: _appSetup,
|
future: _appSetup,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
final home = (!snapshot.hasError && snapshot.connectionState == ConnectionState.done)
|
||||||
|
? getFirstPage()
|
||||||
|
: Scaffold(
|
||||||
|
body: snapshot.hasError ? _buildError(snapshot.error) : SizedBox.shrink(),
|
||||||
|
);
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
navigatorKey: _navigatorKey,
|
navigatorKey: _navigatorKey,
|
||||||
home: home,
|
home: home,
|
||||||
navigatorObservers: [
|
navigatorObservers: _navigatorObservers,
|
||||||
if (!snapshot.hasError && snapshot.connectionState == ConnectionState.done) _routeTracker,
|
|
||||||
],
|
|
||||||
title: 'Aves',
|
title: 'Aves',
|
||||||
darkTheme: darkTheme,
|
darkTheme: darkTheme,
|
||||||
themeMode: ThemeMode.dark,
|
themeMode: ThemeMode.dark,
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:aves/model/settings/coordinate_format.dart';
|
||||||
import 'package:aves/model/settings/home_page.dart';
|
import 'package:aves/model/settings/home_page.dart';
|
||||||
import 'package:aves/model/settings/screen_on.dart';
|
import 'package:aves/model/settings/screen_on.dart';
|
||||||
import 'package:aves/widgets/fullscreen/info/location_section.dart';
|
import 'package:aves/widgets/fullscreen/info/location_section.dart';
|
||||||
|
import 'package:firebase_analytics/firebase_analytics.dart';
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
@ -57,9 +58,14 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
// Crashlytics initialization is separated from the main settings initialization
|
// Crashlytics initialization is separated from the main settings initialization
|
||||||
// to allow settings customization without Firebase context (e.g. before a Flutter Driver test)
|
// to allow settings customization without Firebase context (e.g. before a Flutter Driver test)
|
||||||
Future<void> initCrashlytics() async {
|
Future<void> initFirebase() async {
|
||||||
await Firebase.app().setAutomaticDataCollectionEnabled(isCrashlyticsEnabled);
|
await Firebase.app().setAutomaticDataCollectionEnabled(isCrashlyticsEnabled);
|
||||||
await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(isCrashlyticsEnabled);
|
await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(isCrashlyticsEnabled);
|
||||||
|
await FirebaseAnalytics().setAnalyticsCollectionEnabled(isCrashlyticsEnabled);
|
||||||
|
// enable analytics debug mode:
|
||||||
|
// # %ANDROID_SDK%/platform-tools/adb shell setprop debug.firebase.analytics.app deckers.thibault.aves.debug
|
||||||
|
// disable analytics debug mode:
|
||||||
|
// # %ANDROID_SDK%/platform-tools/adb shell setprop debug.firebase.analytics.app .none.
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> reset() {
|
Future<void> reset() {
|
||||||
|
@ -76,7 +82,7 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
set isCrashlyticsEnabled(bool newValue) {
|
set isCrashlyticsEnabled(bool newValue) {
|
||||||
setAndNotify(isCrashlyticsEnabledKey, newValue);
|
setAndNotify(isCrashlyticsEnabledKey, newValue);
|
||||||
unawaited(initCrashlytics());
|
unawaited(initFirebase());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get mustBackTwiceToExit => getBoolOrDefault(mustBackTwiceToExitKey, true);
|
bool get mustBackTwiceToExit => getBoolOrDefault(mustBackTwiceToExitKey, true);
|
||||||
|
|
|
@ -13,6 +13,7 @@ import 'package:aves/utils/file_utils.dart';
|
||||||
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
|
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:aves/widgets/fullscreen/info/info_page.dart';
|
import 'package:aves/widgets/fullscreen/info/info_page.dart';
|
||||||
|
import 'package:firebase_analytics/firebase_analytics.dart';
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -109,6 +110,21 @@ class AppDebugPageState extends State<AppDebugPage> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text('Analytics'),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => FirebaseAnalytics().logEvent(
|
||||||
|
name: 'debug_test',
|
||||||
|
parameters: {'time': DateTime.now().toIso8601String()},
|
||||||
|
),
|
||||||
|
child: Text('Send event'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
Text('Firebase data collection: ${Firebase.app().isAutomaticDataCollectionEnabled ? 'enabled' : 'disabled'}'),
|
Text('Firebase data collection: ${Firebase.app().isAutomaticDataCollectionEnabled ? 'enabled' : 'disabled'}'),
|
||||||
Text('Crashlytics collection: ${FirebaseCrashlytics.instance.isCrashlyticsCollectionEnabled ? 'enabled' : 'disabled'}'),
|
Text('Crashlytics collection: ${FirebaseCrashlytics.instance.isCrashlyticsCollectionEnabled ? 'enabled' : 'disabled'}'),
|
||||||
Divider(),
|
Divider(),
|
||||||
|
|
|
@ -6,8 +6,10 @@ import 'package:aves/model/metadata_db.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/services/image_file_service.dart';
|
import 'package:aves/services/image_file_service.dart';
|
||||||
|
import 'package:firebase_analytics/firebase_analytics.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_native_timezone/flutter_native_timezone.dart';
|
import 'package:flutter_native_timezone/flutter_native_timezone.dart';
|
||||||
|
import 'package:pedantic/pedantic.dart';
|
||||||
|
|
||||||
class MediaStoreSource extends CollectionSource {
|
class MediaStoreSource extends CollectionSource {
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
|
@ -68,16 +70,31 @@ class MediaStoreSource extends CollectionSource {
|
||||||
onDone: () async {
|
onDone: () async {
|
||||||
addPendingEntries();
|
addPendingEntries();
|
||||||
debugPrint('$runtimeType refresh loaded ${allNewEntries.length} new entries, elapsed=${stopwatch.elapsed}');
|
debugPrint('$runtimeType refresh loaded ${allNewEntries.length} new entries, elapsed=${stopwatch.elapsed}');
|
||||||
|
|
||||||
await metadataDb.saveEntries(allNewEntries); // 700ms for 5500 entries
|
await metadataDb.saveEntries(allNewEntries); // 700ms for 5500 entries
|
||||||
updateAlbums();
|
updateAlbums();
|
||||||
|
final analytics = FirebaseAnalytics();
|
||||||
|
unawaited(analytics.setUserProperty(name: 'local_item_count', value: (ceilBy(rawEntries.length, 3)).toString()));
|
||||||
|
unawaited(analytics.setUserProperty(name: 'album_count', value: (ceilBy(sortedAlbums.length, 1)).toString()));
|
||||||
|
|
||||||
stateNotifier.value = SourceState.cataloguing;
|
stateNotifier.value = SourceState.cataloguing;
|
||||||
await catalogEntries();
|
await catalogEntries();
|
||||||
|
unawaited(analytics.setUserProperty(name: 'tag_count', value: (ceilBy(sortedTags.length, 1)).toString()));
|
||||||
|
|
||||||
stateNotifier.value = SourceState.locating;
|
stateNotifier.value = SourceState.locating;
|
||||||
await locateEntries();
|
await locateEntries();
|
||||||
|
unawaited(analytics.setUserProperty(name: 'country_count', value: (ceilBy(sortedCountries.length, 1)).toString()));
|
||||||
|
|
||||||
stateNotifier.value = SourceState.ready;
|
stateNotifier.value = SourceState.ready;
|
||||||
debugPrint('$runtimeType refresh done, elapsed=${stopwatch.elapsed}');
|
debugPrint('$runtimeType refresh done, elapsed=${stopwatch.elapsed}');
|
||||||
},
|
},
|
||||||
onError: (error) => debugPrint('$runtimeType stream error=$error'),
|
onError: (error) => debugPrint('$runtimeType stream error=$error'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// e.g. x=12345, precision=3 should return 13000
|
||||||
|
int ceilBy(num x, int precision) {
|
||||||
|
final factor = pow(10, precision);
|
||||||
|
return (x / factor).ceil() * factor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ abstract class ChipSetActionDelegate {
|
||||||
options: {
|
options: {
|
||||||
ChipSortFactor.date: 'By date',
|
ChipSortFactor.date: 'By date',
|
||||||
ChipSortFactor.name: 'By name',
|
ChipSortFactor.name: 'By name',
|
||||||
ChipSortFactor.count: 'By entry count',
|
ChipSortFactor.count: 'By item count',
|
||||||
},
|
},
|
||||||
title: 'Sort',
|
title: 'Sort',
|
||||||
),
|
),
|
||||||
|
|
|
@ -115,7 +115,7 @@ class SettingsPage extends StatelessWidget {
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
value: settings.isCrashlyticsEnabled,
|
value: settings.isCrashlyticsEnabled,
|
||||||
onChanged: (v) => settings.isCrashlyticsEnabled = v,
|
onChanged: (v) => settings.isCrashlyticsEnabled = v,
|
||||||
title: Text('Allow anonymous crash reporting'),
|
title: Text('Allow anonymous analytics and crash reporting'),
|
||||||
),
|
),
|
||||||
GrantedDirectories(),
|
GrantedDirectories(),
|
||||||
],
|
],
|
||||||
|
|
|
@ -99,7 +99,7 @@ class _WelcomePageState extends State<WelcomePage> {
|
||||||
LabeledCheckbox(
|
LabeledCheckbox(
|
||||||
value: settings.isCrashlyticsEnabled,
|
value: settings.isCrashlyticsEnabled,
|
||||||
onChanged: (v) => setState(() => settings.isCrashlyticsEnabled = v),
|
onChanged: (v) => setState(() => settings.isCrashlyticsEnabled = v),
|
||||||
text: 'Allow anonymous crash reporting',
|
text: 'Allow anonymous analytics and crash reporting',
|
||||||
),
|
),
|
||||||
LabeledCheckbox(
|
LabeledCheckbox(
|
||||||
key: Key('agree-checkbox'),
|
key: Key('agree-checkbox'),
|
||||||
|
|
21
pubspec.lock
21
pubspec.lock
|
@ -201,6 +201,27 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.3.2"
|
version: "7.3.2"
|
||||||
|
firebase_analytics:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: firebase_analytics
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.2"
|
||||||
|
firebase_analytics_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_analytics_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
|
firebase_analytics_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_analytics_web
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.1"
|
||||||
firebase_core:
|
firebase_core:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -49,6 +49,7 @@ dependencies:
|
||||||
git:
|
git:
|
||||||
url: git://github.com/deckerst/expansion_tile_card.git
|
url: git://github.com/deckerst/expansion_tile_card.git
|
||||||
firebase_core:
|
firebase_core:
|
||||||
|
firebase_analytics:
|
||||||
firebase_crashlytics:
|
firebase_crashlytics:
|
||||||
flushbar:
|
flushbar:
|
||||||
flutter_ijkplayer:
|
flutter_ijkplayer:
|
||||||
|
|
Loading…
Reference in a new issue