Skip to content
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

[PLAT-8383] Configure bugsnag_flutter from native layer #145

Merged
merged 4 commits into from
Jun 23, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ package com.example.bugsnag.flutter.android

import android.app.Application
import com.bugsnag.android.Bugsnag
import com.bugsnag.flutter.BugsnagFlutterConfiguration

class ExampleApp : Application() {
override fun onCreate() {
super.onCreate()

// Start Bugsnag Android SDK
Bugsnag.start(this)

// Uncomment to disable automatic detection of Dart errors:
// BugsnagFlutterConfiguration.enabledErrorTypes.dartErrors = false
}
}
3 changes: 3 additions & 0 deletions examples/native/ios/BugsnagFlutter/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ class AppDelegate: FlutterAppDelegate {
// Start Bugsnag iOS SDK
Bugsnag.start()

// Uncomment to disable automatic detection of Dart errors:
// BugsnagFlutterConfiguration.enabledErrorTypes.dartErrors = false

// Runs the default Dart entrypoint with a default Flutter route.
flutterEngine.run();

Expand Down
23 changes: 22 additions & 1 deletion features/attach_flutter.feature
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ Feature: Attach to running native Bugsnag instance
Then I wait to receive an error
And the error is valid for the error reporting API version "4.0" for the "Flutter Bugsnag Notifier" notifier
And the exception "errorClass" equals "_Exception"
And the exception "message" equals "Handled exception with attached info"
And the error payload field "events.0.unhandled" is false
And the error payload field "events.0.exceptions.0.message" equals "Exception with attached info"
And the error payload field "events.0.threads" is a non-empty array
And the event "severity" equals "warning"

Expand All @@ -18,3 +18,24 @@ Feature: Attach to running native Bugsnag instance
And the event "context" equals "flutter-test-context"
And event 0 contains the feature flag "demo-mode" with no variant
And event 0 contains the feature flag "sample-group" with variant "123"

Scenario: attach with an unhandled exception
When I run "AttachBugsnagScenario"
Then I wait to receive an error
And the error is valid for the error reporting API version "4.0" for the "Flutter Bugsnag Notifier" notifier
And the exception "errorClass" equals "_Exception"
And the exception "message" equals "Unhandled exception with attached info"
And the error payload field "events.0.threads" is a non-empty array
And the event "context" equals "flutter-test-context"
And the event "severity" equals "error"
And the event "unhandled" is true
And the event "user.email" is null
And the event "user.id" equals "test-user-id"
And the event "user.name" equals "Old Man Tables"
And event 0 contains the feature flag "demo-mode" with no variant
And event 0 contains the feature flag "sample-group" with variant "123"

Scenario: attach with Dart error detection disabled
When I configure the app to run in the "disableDartErrors" state
And I run "AttachBugsnagScenario"
Then I should receive no errors
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.bugsnag.android.Bugsnag;
import com.bugsnag.android.Configuration;
import com.bugsnag.android.EndpointConfiguration;
import com.bugsnag.flutter.BugsnagFlutterConfiguration;
import com.bugsnag.flutter.test.app.scenario.Scenario;

import java.io.BufferedReader;
Expand Down Expand Up @@ -47,13 +48,21 @@ public void run() {
} else if (call.method.equals("startBugsnag")) {
Configuration config = Configuration.load(context);
config.setApiKey("abc12312312312312312312312312312");
if (call.hasArgument("notifyEndpoint") && call.hasArgument("sessionEndpoint")) {
config.setEndpoints(new EndpointConfiguration(
call.argument("notifyEndpoint"),
call.argument("sessionEndpoint")
));

String notifyEndpoint = call.argument("notifyEndpoint");
String sessionEndpoint = call.argument("sessionEndpoint");
String extraConfig = call.argument("extraConfig");

if (notifyEndpoint != null && sessionEndpoint != null) {
config.setEndpoints(new EndpointConfiguration(notifyEndpoint, sessionEndpoint));
}

Bugsnag.start(context, config);

if (extraConfig != null && extraConfig.contains("disableDartErrors")) {
BugsnagFlutterConfiguration.enabledErrorTypes.dartErrors = false;
}

result.success(null);
} else if (call.method.equals("clearPersistentData")) {
clearPersistentData();
Expand Down
7 changes: 7 additions & 0 deletions features/fixtures/app/ios/Runner/AppDelegate.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#import <Bugsnag/Bugsnag.h>
#import <bugsnag_flutter/BugsnagFlutterConfiguration.h>

#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
Expand Down Expand Up @@ -51,13 +52,19 @@ -(void)onMethod:(FlutterMethodCall*) call

NSString *notifyEndpoint = call.arguments[@"notifyEndpoint"];
NSString *sessionEndpoint = call.arguments[@"sessionEndpoint"];
NSString *extraConfig = call.arguments[@"extraConfig"];

if(notifyEndpoint != nil && sessionEndpoint != nil) {
config.endpoints = [[BugsnagEndpointConfiguration alloc] initWithNotify:notifyEndpoint
sessions:sessionEndpoint];
}

[Bugsnag startWithConfiguration:config];

if ([extraConfig containsString:@"disableDartErrors"]) {
BugsnagFlutterConfiguration.enabledErrorTypes.dartErrors = NO;
}

result(nil);
} else if ([@"clearPersistentData" isEqual:call.method]) {
[NSUserDefaults.standardUserDefaults removePersistentDomainForName:NSBundle.mainBundle.bundleIdentifier];
Expand Down
6 changes: 4 additions & 2 deletions features/fixtures/app/lib/channels.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@ class MazeRunnerChannels {
static Future<void> runScenario(String scenarioName, {String? extraConfig}) {
return platform.invokeMethod("runScenario", {
'scenarioName': scenarioName,
'extraConfig': extraConfig,
if (extraConfig != null) 'extraConfig': extraConfig,
});
}

static Future<void> startBugsnag(BugsnagEndpointConfiguration endpoints) {
static Future<void> startBugsnag(BugsnagEndpointConfiguration endpoints,
{String? extraConfig}) {
return platform.invokeMethod("startBugsnag", {
'notifyEndpoint': endpoints.notify,
'sessionEndpoint': endpoints.sessions,
if (extraConfig != null) 'extraConfig': extraConfig,
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ class AttachBugsnagScenario extends Scenario {

if (extraConfig?.contains("handled") == true) {
await bugsnag.notify(
Exception('Exception with attached info'),
Exception('Handled exception with attached info'),
StackTrace.current,
);
} else {
throw Exception('Exception with attached info');
throw Exception('Unhandled exception with attached info');
}
},
);
Expand Down
2 changes: 1 addition & 1 deletion features/fixtures/app/lib/scenarios/scenario.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ abstract class Scenario {
}

Future<void> startNativeNotifier() =>
MazeRunnerChannels.startBugsnag(endpoints);
MazeRunnerChannels.startBugsnag(endpoints, extraConfig: extraConfig);

Future<void> startBugsnag() => bugsnag.start(endpoints: endpoints);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,21 @@ class BugsnagFlutter {

Context context;

Void attach(@NonNull JSONObject args) throws Exception {
JSONObject attach(@NonNull JSONObject args) throws Exception {
if (isAttached) {
throw new IllegalStateException("bugsnag.attach() may not be called more than once");
}

JSONObject result = new JSONObject()
.put("config", new JSONObject()
.put("enabledErrorTypes", new JSONObject()
.put("dartErrors", BugsnagFlutterConfiguration.enabledErrorTypes.dartErrors)
)
);

if (isAnyAttached) {
Log.i("BugsnagFlutter", "bugsnag.attach() was called from a previous Flutter context. Ignoring.");
return null;
return result;
}

Client nativeClient = InternalHooks.getClient();
Expand All @@ -78,7 +85,7 @@ Void attach(@NonNull JSONObject args) throws Exception {

isAnyAttached = true;
isAttached = true;
return null;
return result;
}

Void start(@NonNull JSONObject args) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.bugsnag.flutter;

public class BugsnagFlutterConfiguration {
public static BugsnagFlutterEnabledErrorTypes enabledErrorTypes = new BugsnagFlutterEnabledErrorTypes();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.bugsnag.flutter;

public class BugsnagFlutterEnabledErrorTypes {
public boolean dartErrors = true;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import "BugsnagFlutterEnabledErrorTypes.h"

@interface BugsnagFlutterConfiguration : NSObject

@property (class, readonly, nonnull) BugsnagFlutterEnabledErrorTypes *enabledErrorTypes;

@end
15 changes: 15 additions & 0 deletions packages/bugsnag_flutter/ios/Classes/BugsnagFlutterConfiguration.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#import "BugsnagFlutterConfiguration.h"

@implementation BugsnagFlutterConfiguration

static BugsnagFlutterEnabledErrorTypes *enabledErrorTypes;

+ (void)initialize {
enabledErrorTypes = [[BugsnagFlutterEnabledErrorTypes alloc] init];
}

+ (BugsnagFlutterEnabledErrorTypes *)enabledErrorTypes {
return enabledErrorTypes;
}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import <Foundation/Foundation.h>

@interface BugsnagFlutterEnabledErrorTypes : NSObject

@property (nonatomic) BOOL dartErrors;

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#import "BugsnagFlutterEnabledErrorTypes.h"

@implementation BugsnagFlutterEnabledErrorTypes

- (instancetype)init {
if ((self = [super init])) {
_dartErrors = YES;
}
return self;
}

@end
36 changes: 1 addition & 35 deletions packages/bugsnag_flutter/ios/Classes/BugsnagFlutterPlugin.h
Original file line number Diff line number Diff line change
@@ -1,40 +1,6 @@
#import <Flutter/Flutter.h>

/// Declares the methods exposed to Flutter
@protocol BugsnagFlutterProtocol

- (void)setUser:(NSDictionary *)json;
- (NSDictionary *)getUser:(NSDictionary *)json;

- (void)setContext:(NSDictionary *)json;
- (NSString *)getContext:(NSDictionary *)json;

- (void)leaveBreadcrumb:(NSDictionary *)arguments;
- (NSArray<NSDictionary *> *)getBreadcrumbs:(NSDictionary *)arguments;

- (void)addFeatureFlags:(NSArray *)featureFlags;
- (void)clearFeatureFlag:(NSDictionary *)arguments;
- (void)clearFeatureFlags:(NSDictionary *)arguments;

- (void)addMetadata:(NSDictionary *)arguments;
- (void)clearMetadata:(NSDictionary *)arguments;
- (NSDictionary *)getMetadata:(NSDictionary *)arguments;

- (void)attach:(NSDictionary *)json;

- (void)start:(NSDictionary *)arguments;

- (void)startSession:(NSDictionary *)arguments;
- (void)pauseSession:(NSDictionary *)arguments;
- (NSNumber *)resumeSession:(NSDictionary *)arguments;

- (void)markLaunchCompleted:(NSDictionary *)arguments;
- (NSDictionary *)getLastRunInfo:(NSDictionary *)arguments;

- (NSDictionary *)createEvent:(NSDictionary *)json;
- (void)deliverEvent:(NSDictionary *)json;

@end
#import "BugsnagFlutterProtocol.h"

@interface BugsnagFlutterPlugin : NSObject<FlutterPlugin, BugsnagFlutterProtocol>

Expand Down
14 changes: 12 additions & 2 deletions packages/bugsnag_flutter/ios/Classes/BugsnagFlutterPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#import "BugsnagConfiguration+Private.h"
#import "BugsnagError+Private.h"
#import "BugsnagEvent+Private.h"
#import "BugsnagFlutterConfiguration.h"
#import "BugsnagHandledState.h"
#import "BugsnagNotifier.h"
#import "BugsnagSessionTracker.h"
Expand Down Expand Up @@ -209,15 +210,23 @@ - (NSDictionary *)getMetadata:(NSDictionary *)arguments {
return [Bugsnag getMetadataFromSection:arguments[@"section"]];
}

- (void)attach:(NSDictionary *)arguments {
- (NSDictionary *)attach:(NSDictionary *)arguments {
if (self.isAttached) {
[NSException raise:NSInternalInconsistencyException format:@"bugsnag.attach() may not be called more than once"];
}

NSDictionary *result = @{
@"config": @{
@"enabledErrorTypes": @{
@"dartErrors": BugsnagFlutterConfiguration.enabledErrorTypes.dartErrors ? @YES : @NO
}
}
};

static BOOL isAnyAttached;
if (isAnyAttached) {
NSLog(@"bugsnag.attach() was called from a previous Flutter context. Ignoring.");
return;
return result;
}

if (!Bugsnag.client) {
Expand All @@ -233,6 +242,7 @@ - (void)attach:(NSDictionary *)arguments {

isAnyAttached = YES;
self.attached = YES;
return result;
}

- (void)start:(NSDictionary *)arguments {
Expand Down
36 changes: 36 additions & 0 deletions packages/bugsnag_flutter/ios/Classes/BugsnagFlutterProtocol.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#import <Foundation/Foundation.h>

@protocol BugsnagFlutterProtocol

- (void)setUser:(NSDictionary *)json;
- (NSDictionary *)getUser:(NSDictionary *)json;

- (void)setContext:(NSDictionary *)json;
- (NSString *)getContext:(NSDictionary *)json;

- (void)leaveBreadcrumb:(NSDictionary *)arguments;
- (NSArray<NSDictionary *> *)getBreadcrumbs:(NSDictionary *)arguments;

- (void)addFeatureFlags:(NSArray *)featureFlags;
- (void)clearFeatureFlag:(NSDictionary *)arguments;
- (void)clearFeatureFlags:(NSDictionary *)arguments;

- (void)addMetadata:(NSDictionary *)arguments;
- (void)clearMetadata:(NSDictionary *)arguments;
- (NSDictionary *)getMetadata:(NSDictionary *)arguments;

- (NSDictionary *)attach:(NSDictionary *)json;

- (void)start:(NSDictionary *)arguments;

- (void)startSession:(NSDictionary *)arguments;
- (void)pauseSession:(NSDictionary *)arguments;
- (NSNumber *)resumeSession:(NSDictionary *)arguments;

- (void)markLaunchCompleted:(NSDictionary *)arguments;
- (NSDictionary *)getLastRunInfo:(NSDictionary *)arguments;

- (NSDictionary *)createEvent:(NSDictionary *)json;
- (void)deliverEvent:(NSDictionary *)json;

@end
Loading