Merge branch 'develop' into fix/channel-unset
This commit is contained in:
@@ -159,7 +159,6 @@ linter:
|
||||
- avoid_escaping_inner_quotes
|
||||
- unnecessary_overrides
|
||||
- prefer_null_aware_method_calls
|
||||
- prefer_null_aware_operators
|
||||
- use_named_constants
|
||||
- use_raw_strings
|
||||
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
---
|
||||
id: error_reporting_with_sentry
|
||||
sidebar_position: 15
|
||||
title: Error Reporting With Sentry
|
||||
---
|
||||
|
||||
Error Reporting With Sentry
|
||||
|
||||
## Introduction
|
||||
|
||||
While one always tries to create apps that are free of bugs, they're sure to crop up from time to time. Since buggy apps lead to unhappy users and customers, it's important to understand how often your users experience bugs and where those bugs occur. That way, you can prioritize the bugs with the highest impact and work to fix them.
|
||||
|
||||
Whenever an error occurs, create a report containing the error that occurred and the associated stack trace. You can then send the report to an error tracking service, such as [Sentry](https://sentry.io/), [Rollbar](https://rollbar.com/), or [Firebase Crashlytics](https://firebase.google.com/docs/crashlytics).
|
||||
|
||||
The error tracking service aggregates all of the crashes your users experience and groups them together. This allows you to know how often your app fails and where your users run into trouble.
|
||||
|
||||
In this guide, learn how to report Stream Chat errors to the [Sentry](https://sentry.io/welcome/) crash reporting service using the following steps.
|
||||
|
||||
### 1. Get a DSN From Sentry
|
||||
|
||||
Before reporting errors to Sentry, you need a “DSN” to uniquely identify your app with the Sentry service:
|
||||
To get a DSN, use the following steps:
|
||||
|
||||
- [Create an account with Sentry](https://sentry.io/signup/).
|
||||
- Log in to the account.
|
||||
- Create a new Flutter project.
|
||||
- Copy the code snippet that includes the DSN.
|
||||
|
||||
### 2. Import the Sentry package
|
||||
|
||||
Import the `sentry_flutter` package into your app. The sentry package makes it easier to send error reports to the Sentry error tracking service.
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
sentry_flutter: <latest_version>
|
||||
```
|
||||
|
||||
### 3. Initialize the Sentry SDK
|
||||
|
||||
Initialize the SDK to capture different unhandled errors automatically.
|
||||
|
||||
```dart
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
await SentryFlutter.init(
|
||||
(options) => options.dsn = 'https://[email protected]/example',
|
||||
appRunner: () => runApp(const MyApp()),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Or, if you want to run your app in your own error zone, use `runZonedGuarded`:
|
||||
|
||||
```dart
|
||||
void main() async {
|
||||
/// Captures errors reported by the Flutter framework.
|
||||
FlutterError.onError = (FlutterErrorDetails details) {
|
||||
if (kDebugMode) {
|
||||
// In development mode, simply print to console.
|
||||
FlutterError.dumpErrorToConsole(details);
|
||||
} else {
|
||||
// In production mode, report to the application zone to report to Sentry.
|
||||
Zone.current.handleUncaughtError(details.exception, details.stack!);
|
||||
}
|
||||
};
|
||||
|
||||
Future<void> _reportError(dynamic error, StackTrace stackTrace) async {
|
||||
// Print the exception to the console.
|
||||
if (kDebugMode) {
|
||||
// Print the full stack trace in debug mode.
|
||||
print(stackTrace);
|
||||
return;
|
||||
} else {
|
||||
// Send the Exception and Stacktrace to sentry in Production mode.
|
||||
await Sentry.captureException(error, stackTrace: stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
runZonedGuarded(
|
||||
() async {
|
||||
await SentryFlutter.init(
|
||||
(options) => options.dsn = 'https://[email protected]/example',
|
||||
);
|
||||
runApp(const MyApp());
|
||||
},
|
||||
_reportError,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, you can pass the DSN to Flutter using the **dart-define** tag:
|
||||
|
||||
```bash
|
||||
--dart-define SENTRY_DSN=https://[email protected]/example
|
||||
```
|
||||
|
||||
### 4. Integration With StreamChat Applications
|
||||
|
||||
Override the default `logHandlerFunction` to send errors to Sentry.
|
||||
|
||||
```dart
|
||||
void sampleAppLogHandler(LogRecord record) async {
|
||||
if (kDebugMode) StreamChatClient.defaultLogHandler(record);
|
||||
|
||||
// Report errors to Sentry
|
||||
if (record.error != null || record.stackTrace != null) {
|
||||
await Sentry.captureException(
|
||||
record.error,
|
||||
stackTrace: record.stackTrace,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
StreamChatClient buildStreamChatClient(
|
||||
String apiKey, {
|
||||
Level logLevel = Level.SEVERE,
|
||||
}) {
|
||||
return StreamChatClient(
|
||||
apiKey,
|
||||
logLevel: logLevel,
|
||||
logHandlerFunction: sampleAppLogHandler, // Pass the overridden logHandlerFunction
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Capture Errors Programmatically
|
||||
|
||||
Besides the automatic error reporting that Sentry generates by importing and initializing the SDK,
|
||||
you can use the API to manually report errors to Sentry:
|
||||
|
||||
```dart
|
||||
await Sentry.captureException(exception, stackTrace: stackTrace);
|
||||
```
|
||||
|
||||
For more information, see the [Sentry API](https://pub.dev/documentation/sentry_flutter/latest/sentry_flutter/sentry_flutter-library.html) docs on Pub.
|
||||
|
||||
### Complete Example
|
||||
|
||||
To view a working example, see the [Stream Sample app](https://github.com/GetStream/flutter-samples/tree/main/packages/stream_chat_v1).
|
||||
|
||||
### Learn More
|
||||
|
||||
Extensive documentation about using the Sentry SDK can be found on [Sentry's site](https://docs.sentry.io/platforms/flutter/).
|
||||
@@ -8,6 +8,7 @@
|
||||
- Added `PaginationParams.createdAtBefore` for message pagination.
|
||||
- Added `PaginationParams.createdAtAround` for message pagination.
|
||||
- Added support for `channel.disabled`, `channel.hidden` and `channel.truncatedAt` in `Channel`.
|
||||
- Added support for `channel.membership` and `channel.membershipStream` in `Channel`.
|
||||
|
||||
🔄 Changed
|
||||
|
||||
|
||||
@@ -171,6 +171,18 @@ class Channel {
|
||||
return state!.channelStateStream.map((cs) => cs.channel?.config);
|
||||
}
|
||||
|
||||
/// Relationship of the current user to this channel.
|
||||
Member? get membership {
|
||||
_checkInitialized();
|
||||
return state!._channelState.membership;
|
||||
}
|
||||
|
||||
/// Relationship of the current user to this channel as a stream.
|
||||
Stream<Member?> get membershipStream {
|
||||
_checkInitialized();
|
||||
return state!.channelStateStream.map((cs) => cs.membership);
|
||||
}
|
||||
|
||||
/// Channel user creator.
|
||||
User? get createdBy {
|
||||
_checkInitialized();
|
||||
|
||||
@@ -12,7 +12,7 @@ part of 'attachment_file.dart';
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
||||
|
||||
UploadState _$UploadStateFromJson(Map<String, dynamic> json) {
|
||||
switch (json['runtimeType']) {
|
||||
@@ -31,39 +31,6 @@ UploadState _$UploadStateFromJson(Map<String, dynamic> json) {
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$UploadStateTearOff {
|
||||
const _$UploadStateTearOff();
|
||||
|
||||
Preparing preparing() {
|
||||
return const Preparing();
|
||||
}
|
||||
|
||||
InProgress inProgress({required int uploaded, required int total}) {
|
||||
return InProgress(
|
||||
uploaded: uploaded,
|
||||
total: total,
|
||||
);
|
||||
}
|
||||
|
||||
Success success() {
|
||||
return const Success();
|
||||
}
|
||||
|
||||
Failed failed({required String error}) {
|
||||
return Failed(
|
||||
error: error,
|
||||
);
|
||||
}
|
||||
|
||||
UploadState fromJson(Map<String, Object?> json) {
|
||||
return UploadState.fromJson(json);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
const $UploadState = _$UploadStateTearOff();
|
||||
|
||||
/// @nodoc
|
||||
mixin _$UploadState {
|
||||
@optionalTypeArgs
|
||||
@@ -156,7 +123,7 @@ class __$$PreparingCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res>
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$Preparing implements Preparing {
|
||||
const _$Preparing({String? $type}) : $type = $type ?? 'preparing';
|
||||
const _$Preparing({final String? $type}) : $type = $type ?? 'preparing';
|
||||
|
||||
factory _$Preparing.fromJson(Map<String, dynamic> json) =>
|
||||
_$$PreparingFromJson(json);
|
||||
@@ -175,6 +142,7 @@ class _$Preparing implements Preparing {
|
||||
(other.runtimeType == runtimeType && other is _$Preparing);
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@@ -304,7 +272,7 @@ class __$$InProgressCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res>
|
||||
@JsonSerializable()
|
||||
class _$InProgress implements InProgress {
|
||||
const _$InProgress(
|
||||
{required this.uploaded, required this.total, String? $type})
|
||||
{required this.uploaded, required this.total, final String? $type})
|
||||
: $type = $type ?? 'inProgress';
|
||||
|
||||
factory _$InProgress.fromJson(Map<String, dynamic> json) =>
|
||||
@@ -332,6 +300,7 @@ class _$InProgress implements InProgress {
|
||||
const DeepCollectionEquality().equals(other.total, total));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
@@ -424,14 +393,14 @@ class _$InProgress implements InProgress {
|
||||
}
|
||||
|
||||
abstract class InProgress implements UploadState {
|
||||
const factory InProgress({required int uploaded, required int total}) =
|
||||
_$InProgress;
|
||||
const factory InProgress(
|
||||
{required final int uploaded, required final int total}) = _$InProgress;
|
||||
|
||||
factory InProgress.fromJson(Map<String, dynamic> json) =
|
||||
_$InProgress.fromJson;
|
||||
|
||||
int get uploaded;
|
||||
int get total;
|
||||
int get uploaded => throw _privateConstructorUsedError;
|
||||
int get total => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
_$$InProgressCopyWith<_$InProgress> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
@@ -456,7 +425,7 @@ class __$$SuccessCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res>
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$Success implements Success {
|
||||
const _$Success({String? $type}) : $type = $type ?? 'success';
|
||||
const _$Success({final String? $type}) : $type = $type ?? 'success';
|
||||
|
||||
factory _$Success.fromJson(Map<String, dynamic> json) =>
|
||||
_$$SuccessFromJson(json);
|
||||
@@ -475,6 +444,7 @@ class _$Success implements Success {
|
||||
(other.runtimeType == runtimeType && other is _$Success);
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@@ -596,7 +566,7 @@ class __$$FailedCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res>
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$Failed implements Failed {
|
||||
const _$Failed({required this.error, String? $type})
|
||||
const _$Failed({required this.error, final String? $type})
|
||||
: $type = $type ?? 'failed';
|
||||
|
||||
factory _$Failed.fromJson(Map<String, dynamic> json) =>
|
||||
@@ -621,6 +591,7 @@ class _$Failed implements Failed {
|
||||
const DeepCollectionEquality().equals(other.error, error));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, const DeepCollectionEquality().hash(error));
|
||||
@@ -711,11 +682,11 @@ class _$Failed implements Failed {
|
||||
}
|
||||
|
||||
abstract class Failed implements UploadState {
|
||||
const factory Failed({required String error}) = _$Failed;
|
||||
const factory Failed({required final String error}) = _$Failed;
|
||||
|
||||
factory Failed.fromJson(Map<String, dynamic> json) = _$Failed.fromJson;
|
||||
|
||||
String get error;
|
||||
String get error => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
_$$FailedCopyWith<_$Failed> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
@@ -19,6 +19,7 @@ class ChannelState {
|
||||
this.watcherCount,
|
||||
this.watchers,
|
||||
this.read,
|
||||
this.membership,
|
||||
});
|
||||
|
||||
/// The channel to which this state belongs
|
||||
@@ -42,6 +43,9 @@ class ChannelState {
|
||||
/// The list of channel reads
|
||||
final List<Read>? read;
|
||||
|
||||
/// Relationship of the current user to this channel.
|
||||
final Member? membership;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static ChannelState fromJson(Map<String, dynamic> json) =>
|
||||
_$ChannelStateFromJson(json);
|
||||
@@ -58,6 +62,7 @@ class ChannelState {
|
||||
int? watcherCount,
|
||||
List<User>? watchers,
|
||||
List<Read>? read,
|
||||
Member? membership,
|
||||
}) =>
|
||||
ChannelState(
|
||||
channel: channel ?? this.channel,
|
||||
@@ -67,5 +72,6 @@ class ChannelState {
|
||||
watcherCount: watcherCount ?? this.watcherCount,
|
||||
watchers: watchers ?? this.watchers,
|
||||
read: read ?? this.read,
|
||||
membership: membership ?? this.membership,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ ChannelState _$ChannelStateFromJson(Map<String, dynamic> json) => ChannelState(
|
||||
read: (json['read'] as List<dynamic>?)
|
||||
?.map((e) => Read.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
membership: json['membership'] == null
|
||||
? null
|
||||
: Member.fromJson(json['membership'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ChannelStateToJson(ChannelState instance) =>
|
||||
@@ -38,4 +41,5 @@ Map<String, dynamic> _$ChannelStateToJson(ChannelState instance) =>
|
||||
'watcher_count': instance.watcherCount,
|
||||
'watchers': instance.watchers?.map((e) => e.toJson()).toList(),
|
||||
'read': instance.read?.map((e) => e.toJson()).toList(),
|
||||
'membership': instance.membership?.toJson(),
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
},
|
||||
"watchers": [],
|
||||
"read": [],
|
||||
"membership": null,
|
||||
"messages": [
|
||||
{
|
||||
"id": "dry-meadow-0-2b73cc8b-cd86-4a01-8d40-bd82ad07a030",
|
||||
|
||||
@@ -608,7 +608,6 @@ class StreamMessageInputState extends State<StreamMessageInput>
|
||||
widget: _buildCommandsOverlayEntry(),
|
||||
),
|
||||
if (widget.enableEmojiSuggestionsOverlay &&
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
!widget.disableEmojiSuggestionsOverlay)
|
||||
OverlayOptions(
|
||||
visible: _focusNode.hasFocus &&
|
||||
|
||||
Reference in New Issue
Block a user