-
Notifications
You must be signed in to change notification settings - Fork 4k
🐛 [FirebaseCrashlytics] Dashboard stops showing User ID set by ".setUserIdentifier(String identifier)" after the first report/record #10759
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I can reproduce the issue using the firebase_crashlytics example app modified to add the the user id. The full sample is pasted below code sample// ignore_for_file: require_trailing_commas
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'firebase_options.dart';
// Toggle this to cause an async error to be thrown during initialization
// and to test that runZonedGuarded() catches the error
const _kShouldTestAsyncErrorOnInit = false;
// Toggle this for testing Crashlytics in your app locally.
const _kTestingCrashlytics = true;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FlutterError.onError = (errorDetails) {
// If you wish to record a "non-fatal" exception, please use `FirebaseCrashlytics.instance.recordFlutterError` instead
FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails);
};
PlatformDispatcher.instance.onError = (error, stack) {
// If you wish to record a "non-fatal" exception, please remove the "fatal" parameter
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
runApp(MyApp());
}
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late Future<void> _initializeFlutterFireFuture;
Future<void> _testAsyncErrorOnInit() async {
Future<void>.delayed(const Duration(seconds: 2), () {
final List<int> list = <int>[];
print(list[100]);
});
}
// Define an async function to initialize FlutterFire
Future<void> _initializeFlutterFire() async {
if (_kTestingCrashlytics) {
// Force enable crashlytics collection enabled if we're testing it.
await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true);
} else {
// Else only enable it in non-debug builds.
// You could additionally extend this to allow users to opt-in.
await FirebaseCrashlytics.instance
.setCrashlyticsCollectionEnabled(!kDebugMode);
}
if (_kShouldTestAsyncErrorOnInit) {
await _testAsyncErrorOnInit();
}
}
@override
void initState() {
super.initState();
_initializeFlutterFireFuture = _initializeFlutterFire();
}
@override
Widget build(BuildContext context) {
const identifier = 'UUIIIDDDDD';
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Crashlytics example app'),
),
body: FutureBuilder(
future: _initializeFlutterFireFuture,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
if (snapshot.hasError) {
return Center(
child: Text('Error: ${snapshot.error}'),
);
}
return Center(
child: Column(
children: <Widget>[
ElevatedButton(
onPressed: () {
FirebaseCrashlytics.instance
.setCustomKey('example', 'flutterfire');
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text(
'Custom Key "example: flutterfire" has been set \n'
'Key will appear in Firebase Console once an error has been reported.'),
duration: Duration(seconds: 5),
));
},
child: const Text('Key'),
),
ElevatedButton(
onPressed: () {
FirebaseCrashlytics.instance
.log('This is a log example');
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text(
'The message "This is a log example" has been logged \n'
'Message will appear in Firebase Console once an error has been reported.'),
duration: Duration(seconds: 5),
));
},
child: const Text('Log'),
),
ElevatedButton(
onPressed: () async {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text('App will crash is 5 seconds \n'
'Please reopen to send data to Crashlytics'),
duration: Duration(seconds: 5),
));
// Delay crash for 5 seconds
sleep(const Duration(seconds: 5));
// Use FirebaseCrashlytics to throw an error. Use this for
// confirmation that errors are being correctly reported.
FirebaseCrashlytics.instance.crash();
},
child: const Text('Crash'),
),
ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text(
'Thrown error has been caught and sent to Crashlytics.'),
duration: Duration(seconds: 5),
));
// Example of thrown error, it will be caught and sent to
// Crashlytics.
throw StateError('Uncaught error thrown by app');
},
child: const Text('Throw Error'),
),
ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text(
'Uncaught Exception that is handled by second parameter of runZonedGuarded.'),
duration: Duration(seconds: 5),
));
// Example of an exception that does not get caught
// by `FlutterError.onError` but is caught by
// `runZonedGuarded`.
runZonedGuarded(() {
Future<void>.delayed(const Duration(seconds: 2),
() {
final List<int> list = <int>[];
print(list[100]);
});
}, FirebaseCrashlytics.instance.recordError);
},
child: const Text('Async out of bounds'),
),
ElevatedButton(
onPressed: () async {
try {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text('Recorded Error'),
duration: Duration(seconds: 5),
));
throw Error();
} catch (e, s) {
// "reason" will append the word "thrown" in the
// Crashlytics console.
await FirebaseCrashlytics.instance.recordError(e, s,
reason: 'as an example of fatal error',
fatal: true);
}
},
child: const Text('Record Fatal Error'),
),
ElevatedButton(
onPressed: () async {
try {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text('Recorded Error'),
duration: Duration(seconds: 5),
));
throw Error();
} catch (e, s) {
// "reason" will append the word "thrown" in the
// Crashlytics console.
await FirebaseCrashlytics.instance.recordError(e, s,
reason: 'as an example of non-fatal error');
}
},
child: const Text('Record Non-Fatal Error'),
),
ElevatedButton(
onPressed: () async {
try {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text('Test userId'),
duration: Duration(seconds: 5),
));
throw Error();
} catch (e, s) {
// "reason" will append the word "thrown" in the
// Crashlytics console.
await FirebaseCrashlytics.instance
.setUserIdentifier(identifier);
await FirebaseCrashlytics.instance.recordError(e, s,
reason: 'as an example of non-fatal error');
}
},
child:
const Text('Record Non-Fatal Error With user id'),
),
ElevatedButton(
onPressed: () async {
try {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text('Recorded Error'),
duration: Duration(seconds: 5),
));
throw Error();
} catch (e, s) {
// "reason" will append the word "thrown" in the
// Crashlytics console.
await FirebaseCrashlytics.instance
.setUserIdentifier(identifier);
await FirebaseCrashlytics.instance.recordError(e, s,
reason: 'as an example of fatal error',
fatal: true);
}
},
child: const Text('Record Fatal Error with userId'),
),
],
),
);
default:
return const Center(child: Text('Loading'));
}
},
),
),
);
}
} This did not seem to reproduce on ios. See the recordings of the console below recordingsiOSScreen.Recording.2023-04-11.at.10.35.00.movandroidScreen.Recording.2023-04-11.at.11.45.45.mov |
I can reproduce this. I am looking into it soon. |
I found the cause. In the Crashlytics Android SDK, the user id is associated with a session. And with Flutter on-demand fatals, every new fatal is a new session. This means Unity likely has the same issue. I will change the SDK to preserve user id across sessions. |
We will wait for the fix or an alternative workaround when there is one |
Any news when we can expect it to be fixed ? |
Any news of a fix for this yet? |
Hey folks, Sorry for the delay of replying, we are currently working on a fix for this issue. Will share more timeline information later. Thank you for your patience! |
Bug report
After setting the user's identifier for Crashlytics, the identifier stops being sent (or at least it is not shown) to the Crashlytics dashboard after the first report.
Meaning, the first report sent to Crashlytics correctly contains the user identifier that was previously set.
All the subsequent reports do not contain said identifier, even if we set it again right before sending the new report.
The definition what is a "first report" is still not clear to us.
The closest definition is "the first request of the day" because we usually only see the User ID again on the tests of the following day.
We've also tried only setting the User ID once when the user logs in instead of before every report. but the result is the same.
Steps to reproduce
Steps to reproduce the behavior:
Expected behavior
I expect to find the User ID in the Crashlytics Dashboard for every report.
Sample project
Flutter doctor
Run
flutter doctor
and paste the output below:Click To Expand
Flutter dependencies
Run
flutter pub deps -- --style=compact
and paste the output below:Click To Expand
The text was updated successfully, but these errors were encountered: