Add Firebase Crashlytics

This commit is contained in:
Ming Ming 2021-04-17 00:54:02 +08:00
parent e1bfca432e
commit 5473ff5599
12 changed files with 178 additions and 0 deletions

View file

@ -128,3 +128,6 @@ dependencies {
implementation 'com.nkming.nc_photos.np_android_core:np_android_core' implementation 'com.nkming.nc_photos.np_android_core:np_android_core'
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.3" coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.3"
} }
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'

View file

@ -62,5 +62,8 @@
<meta-data <meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID" android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="" /> android:value="" />
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="false" />
</application> </application>
</manifest> </manifest>

View file

@ -8,6 +8,8 @@ buildscript {
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.4.2' classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.14'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'
} }
} }

View file

@ -1,5 +1,9 @@
import 'dart:io';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:event_bus/event_bus.dart'; import 'package:event_bus/event_bus.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:kiwi/kiwi.dart'; import 'package:kiwi/kiwi.dart';
@ -42,6 +46,7 @@ import 'package:nc_photos/entity/tagged_file/data_source.dart';
import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/mobile/android/android_info.dart'; import 'package:nc_photos/mobile/android/android_info.dart';
import 'package:nc_photos/mobile/self_signed_cert_manager.dart'; import 'package:nc_photos/mobile/self_signed_cert_manager.dart';
import 'package:nc_photos/object_extension.dart';
import 'package:nc_photos/platform/features.dart' as features; import 'package:nc_photos/platform/features.dart' as features;
import 'package:nc_photos/session_storage.dart'; import 'package:nc_photos/session_storage.dart';
import 'package:nc_photos/touch_manager.dart'; import 'package:nc_photos/touch_manager.dart';
@ -85,6 +90,7 @@ Future<void> init(InitIsolateType isolateType) async {
// init session storage // init session storage
SessionStorage(); SessionStorage();
await _initFirebase();
await _initAds(); await _initAds();
_hasInitedInThisIsolate = true; _hasInitedInThisIsolate = true;
@ -98,6 +104,16 @@ void initLog() {
np_log.initLog( np_log.initLog(
isDebugMode: np_log.isDevMode, isDebugMode: np_log.isDevMode,
print: (log) => debugPrint(log, wrapWidth: 1024), print: (log) => debugPrint(log, wrapWidth: 1024),
onLog: (record) {
if (_shouldReportCrashlytics(record)) {
FirebaseCrashlytics.instance.recordError(
record.error,
record.stackTrace,
reason: record.message,
printDetails: false,
);
}
},
); );
} }
@ -236,5 +252,39 @@ Future<InitializationStatus> _initAds() {
return MobileAds.instance.initialize(); return MobileAds.instance.initialize();
} }
Future<void> _initFirebase() async {
await Firebase.initializeApp();
// Crashlytics
if (features.isSupportCrashlytics) {
if (kDebugMode) {
await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(false);
await FirebaseCrashlytics.instance.deleteUnsentReports();
}
}
}
bool _shouldReportCrashlytics(LogRecord record) {
if (kDebugMode ||
!features.isSupportCrashlytics ||
record.level < Level.SHOUT) {
return false;
}
final e = record.error;
// We ignore these SocketExceptions as they are likely caused by an unstable
// internet connection
// 7: No address associated with hostname
// 101: Network is unreachable
// 103: Software caused connection abort
// 104: Connection reset by peer
// 110: Connection timed out
// 113: No route to host
if (e is SocketException &&
e.osError?.errorCode.isIn([7, 101, 103, 104, 110, 113]) == true) {
return false;
}
return true;
}
final _log = Logger("app_init"); final _log = Logger("app_init");
var _hasInitedInThisIsolate = false; var _hasInitedInThisIsolate = false;

View file

@ -7,6 +7,8 @@
"settingsPrivacyTitle": "Privacy", "settingsPrivacyTitle": "Privacy",
"settingsPrivacyDescription": "Privacy-related settings", "settingsPrivacyDescription": "Privacy-related settings",
"settingsPrivacyPageTitle": "Privacy settings", "settingsPrivacyPageTitle": "Privacy settings",
"settingsAnalyticsTitle": "Analytics",
"settingsAnalyticsSubtitle": "Collect analytics after an error to help developers better diagnose the issue",
"settingsPrivacyPolicyTitle": "Privacy policy", "settingsPrivacyPolicyTitle": "Privacy policy",
"setupPrivacyAgreeStatement": "Please read carefully the above privacy policy. By continuing, you agree to our privacy policy", "setupPrivacyAgreeStatement": "Please read carefully the above privacy policy. By continuing, you agree to our privacy policy",
"photosTabLabel": "Photos", "photosTabLabel": "Photos",

View file

@ -3,4 +3,7 @@ import 'package:np_common/object_util.dart';
extension ObjectExtension<T> on T { extension ObjectExtension<T> on T {
/// Deprecated, use [let] /// Deprecated, use [let]
U run<U>(U Function(T obj) fn) => let(fn); U run<U>(U Function(T obj) fn) => let(fn);
/// Return if this is contained inside [iterable]
bool isIn(Iterable<T> iterable) => iterable.contains(this);
} }

View file

@ -6,3 +6,4 @@ final isSupportSelfSignedCert = getRawPlatform() == NpPlatform.android;
final isSupportEnhancement = getRawPlatform() == NpPlatform.android; final isSupportEnhancement = getRawPlatform() == NpPlatform.android;
final isSupportAds = getRawPlatform() != NpPlatform.web; final isSupportAds = getRawPlatform() != NpPlatform.web;
final isSupportCrashlytics = getRawPlatform() != NpPlatform.web;

View file

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
@ -272,6 +273,15 @@ class _PrivacySettings extends StatefulWidget {
} }
class _PrivacySettingsState extends State<_PrivacySettings> { class _PrivacySettingsState extends State<_PrivacySettings> {
@override
initState() {
super.initState();
if (features.isSupportCrashlytics) {
_isEnableAnalytics =
FirebaseCrashlytics.instance.isCrashlyticsCollectionEnabled;
}
}
@override @override
build(BuildContext context) { build(BuildContext context) {
return Scaffold( return Scaffold(
@ -291,6 +301,13 @@ class _PrivacySettingsState extends State<_PrivacySettings> {
SliverList( SliverList(
delegate: SliverChildListDelegate( delegate: SliverChildListDelegate(
[ [
if (features.isSupportCrashlytics)
SwitchListTile(
title: Text(L10n.global().settingsAnalyticsTitle),
subtitle: Text(L10n.global().settingsAnalyticsSubtitle),
value: _isEnableAnalytics,
onChanged: (value) => _onAnalyticsChanged(value),
),
ListTile( ListTile(
title: Text(L10n.global().settingsPrivacyPolicyTitle), title: Text(L10n.global().settingsPrivacyPolicyTitle),
onTap: () { onTap: () {
@ -303,4 +320,13 @@ class _PrivacySettingsState extends State<_PrivacySettings> {
], ],
); );
} }
void _onAnalyticsChanged(bool value) {
setState(() {
_isEnableAnalytics = value;
});
FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(value);
}
late bool _isEnableAnalytics;
} }

View file

@ -1,9 +1,12 @@
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/controller/pref_controller.dart'; import 'package:nc_photos/controller/pref_controller.dart';
import 'package:nc_photos/entity/pref.dart'; import 'package:nc_photos/entity/pref.dart';
import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/platform/features.dart' as features;
import 'package:nc_photos/url_launcher_util.dart'; import 'package:nc_photos/url_launcher_util.dart';
import 'package:nc_photos/widget/home.dart'; import 'package:nc_photos/widget/home.dart';
import 'package:nc_photos/widget/sign_in.dart'; import 'package:nc_photos/widget/sign_in.dart';
@ -250,6 +253,17 @@ class _PrivacyState extends State<_Privacy> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SwitchListTile(
title: Text(L10n.global().settingsAnalyticsTitle),
value: _isEnableAnalytics,
onChanged: _onAnalyticsValueChanged,
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(L10n.global().settingsAnalyticsSubtitle),
),
const SizedBox(height: 16),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: InkWell( child: InkWell(
@ -286,4 +300,25 @@ class _PrivacyState extends State<_Privacy> {
), ),
); );
} }
@override
dispose() {
super.dispose();
// persist user's choice
_log.info("[dispose] Analytics: $_isEnableAnalytics");
if (features.isSupportCrashlytics) {
FirebaseCrashlytics.instance
.setCrashlyticsCollectionEnabled(_isEnableAnalytics);
}
}
void _onAnalyticsValueChanged(bool value) {
setState(() {
_isEnableAnalytics = value;
});
}
bool _isEnableAnalytics = true;
static final _log = Logger("widget.setup._PrivacyState");
} }

View file

@ -9,6 +9,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "67.0.0" version: "67.0.0"
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: "4eec93681221723a686ad580c2e7d960e1017cf1a4e0a263c2573c2c6b0bf5cd"
url: "https://pub.dev"
source: hosted
version: "1.3.25"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
@ -438,6 +446,46 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" version: "7.0.0"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: "53316975310c8af75a96e365f9fccb67d1c544ef0acdbf0d88bbe30eedd1c4f9"
url: "https://pub.dev"
source: hosted
version: "2.27.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63
url: "https://pub.dev"
source: hosted
version: "5.0.0"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: c8e1d59385eee98de63c92f961d2a7062c5d9a65e7f45bdc7f1b0b205aab2492
url: "https://pub.dev"
source: hosted
version: "2.11.5"
firebase_crashlytics:
dependency: "direct main"
description:
name: firebase_crashlytics
sha256: c4f1b723d417bc9c4774810e774ff91df8fb0032d33fb2888b2c887e865581b8
url: "https://pub.dev"
source: hosted
version: "3.4.18"
firebase_crashlytics_platform_interface:
dependency: transitive
description:
name: firebase_crashlytics_platform_interface
sha256: c5a11fca3df76a98e3fa68fde8b10a08aacb9a7639f619fbfd4dad6c67a08643
url: "https://pub.dev"
source: hosted
version: "3.6.25"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:

View file

@ -163,6 +163,8 @@ dependencies:
woozy_search: ^2.0.3 woozy_search: ^2.0.3
# android/ios only # android/ios only
google_mobile_ads: 5.1.0 google_mobile_ads: 5.1.0
firebase_core:
firebase_crashlytics: 3.4.18
dependency_overrides: dependency_overrides:
video_player: video_player:

View file

@ -5,6 +5,7 @@ import 'package:logging/logging.dart';
void initLog({ void initLog({
required bool isDebugMode, required bool isDebugMode,
void Function(String) print = print, void Function(String) print = print,
void Function(LogRecord record)? onLog,
}) { }) {
Logger.root.level = !isDebugMode ? Level.WARNING : Level.ALL; Logger.root.level = !isDebugMode ? Level.WARNING : Level.ALL;
Logger.root.onRecord.listen((record) { Logger.root.onRecord.listen((record) {
@ -35,6 +36,8 @@ void initLog({
} }
print(msg); print(msg);
LogStream().add(msg); LogStream().add(msg);
onLog?.call(record);
}); });
} }