diff --git a/packages/firebase_performance/example/test_driver/firebase_performance.dart b/packages/firebase_performance/example/test_driver/firebase_performance.dart index fcabfe11f239..352db529bd1b 100644 --- a/packages/firebase_performance/example/test_driver/firebase_performance.dart +++ b/packages/firebase_performance/example/test_driver/firebase_performance.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:flutter_driver/driver_extension.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:firebase_performance/firebase_performance.dart'; void main() { final Completer completer = Completer(); @@ -10,6 +9,8 @@ void main() { tearDownAll(() => completer.complete(null)); group('firebase_performance test driver', () { + // TODO(bparrishMines): Rewrite integration tests when iOS portion is written. + /* final FirebasePerformance performance = FirebasePerformance.instance; setUp(() async { @@ -37,5 +38,6 @@ void main() { expect(trace.getAttribute('testAttribute2'), null); expect(trace.getAttribute('testMetric'), null); }); + */ }); } diff --git a/packages/firebase_performance/ios/Classes/FLTFirebasePerformance.m b/packages/firebase_performance/ios/Classes/FLTFirebasePerformance.m new file mode 100644 index 000000000000..81cf29c06559 --- /dev/null +++ b/packages/firebase_performance/ios/Classes/FLTFirebasePerformance.m @@ -0,0 +1,94 @@ +// 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 "FirebasePerformancePlugin+Internal.h" + +@interface FLTFirebasePerformance () +@property FIRPerformance *performance; +@end + +@implementation FLTFirebasePerformance ++ (instancetype _Nonnull)sharedInstance { + static FLTFirebasePerformance *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[FLTFirebasePerformance alloc] init]; + sharedInstance.performance = [FIRPerformance sharedInstance]; + }); + return sharedInstance; +} + +- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { + if ([@"FirebasePerformance#isPerformanceCollectionEnabled" isEqualToString:call.method]) { + [self isPerformanceCollectionEnabled:result]; + } else if ([@"FirebasePerformance#setPerformanceCollectionEnabled" isEqualToString:call.method]) { + [self setPerformanceCollectionEnabled:call result:result]; + } else if ([@"FirebasePerformance#newTrace" isEqualToString:call.method]) { + [self newTrace:call result:result]; + } else if ([@"FirebasePerformance#newHttpMetric" isEqualToString:call.method]) { + [self newHttpMetric:call result:result]; + } else { + result(FlutterMethodNotImplemented); + } +} + +- (void)isPerformanceCollectionEnabled:(FlutterResult)result { + result(@([_performance isDataCollectionEnabled])); +} + +- (void)setPerformanceCollectionEnabled:(FlutterMethodCall *)call result:(FlutterResult)result { + NSNumber *enable = call.arguments[@"enable"]; + [_performance setDataCollectionEnabled:[enable boolValue]]; + result(nil); +} + +- (void)newTrace:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *name = call.arguments[@"name"]; + FIRTrace *trace = [_performance traceWithName:name]; + FLTTrace *handler = [[FLTTrace alloc] initWithTrace:trace]; + + NSNumber *handle = call.arguments[@"traceHandle"]; + [FLTFirebasePerformancePlugin addMethodHandler:handle methodHandler:handler]; + + result(nil); +} + +- (void)newHttpMetric:(FlutterMethodCall *)call result:(FlutterResult)result { + FIRHTTPMethod method = [FLTFirebasePerformance parseHttpMethod:call.arguments[@"httpMethod"]]; + NSURL *url = [NSURL URLWithString:call.arguments[@"url"]]; + + FIRHTTPMetric *metric = [[FIRHTTPMetric alloc] initWithURL:url HTTPMethod:method]; + FLTHttpMetric *handler = [[FLTHttpMetric alloc] initWithHTTPMetric:metric]; + + NSNumber *handle = call.arguments[@"httpMetricHandle"]; + [FLTFirebasePerformancePlugin addMethodHandler:handle methodHandler:handler]; + + result(nil); +} + ++ (FIRHTTPMethod)parseHttpMethod:(NSString *)method { + if ([@"HttpMethod.Connect" isEqualToString:method]) { + return FIRHTTPMethodCONNECT; + } else if ([@"HttpMethod.Delete" isEqualToString:method]) { + return FIRHTTPMethodDELETE; + } else if ([@"HttpMethod.Get" isEqualToString:method]) { + return FIRHTTPMethodGET; + } else if ([@"HttpMethod.Head" isEqualToString:method]) { + return FIRHTTPMethodHEAD; + } else if ([@"HttpMethod.Options" isEqualToString:method]) { + return FIRHTTPMethodOPTIONS; + } else if ([@"HttpMethod.Patch" isEqualToString:method]) { + return FIRHTTPMethodPATCH; + } else if ([@"HttpMethod.Post" isEqualToString:method]) { + return FIRHTTPMethodPOST; + } else if ([@"HttpMethod.Put" isEqualToString:method]) { + return FIRHTTPMethodPUT; + } else if ([@"HttpMethod.Trace" isEqualToString:method]) { + return FIRHTTPMethodTRACE; + } + + NSString *reason = [NSString stringWithFormat:@"Invalid HttpMethod: %@", method]; + @throw [[NSException alloc] initWithName:NSInvalidArgumentException reason:reason userInfo:nil]; +} +@end diff --git a/packages/firebase_performance/ios/Classes/FLTHttpMetric.m b/packages/firebase_performance/ios/Classes/FLTHttpMetric.m new file mode 100644 index 000000000000..3a12f3400613 --- /dev/null +++ b/packages/firebase_performance/ios/Classes/FLTHttpMetric.m @@ -0,0 +1,111 @@ +// 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 "FirebasePerformancePlugin+Internal.h" + +@interface FLTHttpMetric () +@property FIRHTTPMetric *metric; +@end + +@implementation FLTHttpMetric +- (instancetype _Nonnull)initWithHTTPMetric:(FIRHTTPMetric *)metric { + self = [self init]; + if (self) { + _metric = metric; + } + + return self; +} + +- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { + if ([@"HttpMetric#start" isEqualToString:call.method]) { + [self start:result]; + } else if ([@"HttpMetric#stop" isEqualToString:call.method]) { + [self stop:call result:result]; + } else if ([@"HttpMetric#httpResponseCode" isEqualToString:call.method]) { + [self setHttpResponseCode:call result:result]; + } else if ([@"HttpMetric#requestPayloadSize" isEqualToString:call.method]) { + [self requestPayloadSize:call result:result]; + } else if ([@"HttpMetric#responseContentType" isEqualToString:call.method]) { + [self responseContentType:call result:result]; + } else if ([@"HttpMetric#responsePayloadSize" isEqualToString:call.method]) { + [self responsePayloadSize:call result:result]; + } else if ([@"PerformanceAttributes#putAttribute" isEqualToString:call.method]) { + [self putAttribute:call result:result]; + } else if ([@"PerformanceAttributes#removeAttribute" isEqualToString:call.method]) { + [self removeAttribute:call result:result]; + } else if ([@"PerformanceAttributes#getAttributes" isEqualToString:call.method]) { + [self getAttributes:result]; + } else { + result(FlutterMethodNotImplemented); + } +} + +- (void)start:(FlutterResult)result { + [_metric start]; + result(nil); +} + +- (void)stop:(FlutterMethodCall *)call result:(FlutterResult)result { + [_metric stop]; + + NSNumber *handle = call.arguments[@"handle"]; + [FLTFirebasePerformancePlugin removeMethodHandler:handle]; + + result(nil); +} + +- (void)setHttpResponseCode:(FlutterMethodCall *)call result:(FlutterResult)result { + NSNumber *responseCode = call.arguments[@"httpResponseCode"]; + + if (![responseCode isEqual:[NSNull null]]) _metric.responseCode = [responseCode integerValue]; + result(nil); +} + +- (void)requestPayloadSize:(FlutterMethodCall *)call result:(FlutterResult)result { + NSNumber *requestPayloadSize = call.arguments[@"requestPayloadSize"]; + + if (![requestPayloadSize isEqual:[NSNull null]]) { + _metric.requestPayloadSize = [requestPayloadSize longValue]; + } + result(nil); +} + +- (void)responseContentType:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *responseContentType = call.arguments[@"responseContentType"]; + + if (![responseContentType isEqual:[NSNull null]]) { + _metric.responseContentType = responseContentType; + } + result(nil); +} + +- (void)responsePayloadSize:(FlutterMethodCall *)call result:(FlutterResult)result { + NSNumber *responsePayloadSize = call.arguments[@"responsePayloadSize"]; + + if (![responsePayloadSize isEqual:[NSNull null]]) { + _metric.responsePayloadSize = [responsePayloadSize longValue]; + } + result(nil); +} + +- (void)putAttribute:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *name = call.arguments[@"name"]; + NSString *value = call.arguments[@"value"]; + + [_metric setValue:value forAttribute:name]; + result(nil); +} + +- (void)removeAttribute:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *name = call.arguments[@"name"]; + + [_metric removeAttribute:name]; + result(nil); +} + +- (void)getAttributes:(FlutterResult)result { + result([_metric attributes]); +} +@end diff --git a/packages/firebase_performance/ios/Classes/FLTTrace.m b/packages/firebase_performance/ios/Classes/FLTTrace.m new file mode 100644 index 000000000000..3ce6af5fc3ef --- /dev/null +++ b/packages/firebase_performance/ios/Classes/FLTTrace.m @@ -0,0 +1,98 @@ +// 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 "FirebasePerformancePlugin+Internal.h" + +@interface FLTTrace () +@property FIRTrace *trace; +@end + +@implementation FLTTrace +- (instancetype _Nonnull)initWithTrace:(FIRTrace *)trace { + self = [self init]; + if (self) { + _trace = trace; + } + + return self; +} + +- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { + if ([@"Trace#start" isEqualToString:call.method]) { + [self start:result]; + } else if ([@"Trace#stop" isEqualToString:call.method]) { + [self stop:call result:result]; + } else if ([@"Trace#setMetric" isEqualToString:call.method]) { + [self setMetric:call result:result]; + } else if ([@"Trace#incrementMetric" isEqualToString:call.method]) { + [self incrementMetric:call result:result]; + } else if ([@"Trace#getMetric" isEqualToString:call.method]) { + [self getMetric:call result:result]; + } else if ([@"PerformanceAttributes#putAttribute" isEqualToString:call.method]) { + [self putAttribute:call result:result]; + } else if ([@"PerformanceAttributes#removeAttribute" isEqualToString:call.method]) { + [self removeAttribute:call result:result]; + } else if ([@"PerformanceAttributes#getAttributes" isEqualToString:call.method]) { + [self getAttributes:result]; + } else { + result(FlutterMethodNotImplemented); + } +} + +- (void)start:(FlutterResult)result { + [_trace start]; + result(nil); +} + +- (void)stop:(FlutterMethodCall *)call result:(FlutterResult)result { + [_trace stop]; + + NSNumber *handle = call.arguments[@"handle"]; + [FLTFirebasePerformancePlugin removeMethodHandler:handle]; + + result(nil); +} + +- (void)setMetric:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *name = call.arguments[@"name"]; + NSNumber *value = call.arguments[@"value"]; + + [_trace setIntValue:value.longValue forMetric:name]; + result(nil); +} + +- (void)incrementMetric:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *name = call.arguments[@"name"]; + NSNumber *value = call.arguments[@"value"]; + + [_trace incrementMetric:name byInt:value.longValue]; + result(nil); +} + +- (void)getMetric:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *name = call.arguments[@"name"]; + + int64_t metric = [_trace valueForIntMetric:name]; + result(@(metric)); +} + +- (void)putAttribute:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *name = call.arguments[@"name"]; + NSString *value = call.arguments[@"value"]; + + [_trace setValue:value forAttribute:name]; + result(nil); +} + +- (void)removeAttribute:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *name = call.arguments[@"name"]; + + [_trace removeAttribute:name]; + result(nil); +} + +- (void)getAttributes:(FlutterResult)result { + result([_trace attributes]); +} +@end diff --git a/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin+Internal.h b/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin+Internal.h new file mode 100644 index 000000000000..f0adca5b905b --- /dev/null +++ b/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin+Internal.h @@ -0,0 +1,28 @@ +// 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 "FirebasePerformancePlugin.h" + +@protocol MethodCallHandler +@required +- (void)handleMethodCall:(FlutterMethodCall *_Nonnull)call result:(FlutterResult _Nonnull)result; +@end + +@interface FLTFirebasePerformancePlugin (Internal) ++ (void)addMethodHandler:(NSNumber *_Nonnull)handle + methodHandler:(id _Nonnull)handler; ++ (void)removeMethodHandler:(NSNumber *_Nonnull)handle; +@end + +@interface FLTFirebasePerformance : NSObject ++ (instancetype _Nonnull)sharedInstance; +@end + +@interface FLTTrace : NSObject +- (instancetype _Nonnull)initWithTrace:(FIRTrace *_Nonnull)trace; +@end + +@interface FLTHttpMetric : NSObject +- (instancetype _Nonnull)initWithHTTPMetric:(FIRHTTPMetric *_Nonnull)metric; +@end diff --git a/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin.h b/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin.h index d71c5351ac8d..44bf53592eb9 100644 --- a/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin.h +++ b/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin.h @@ -1,3 +1,4 @@ +#import #import @interface FLTFirebasePerformancePlugin : NSObject diff --git a/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin.m b/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin.m index 25073643815e..1c387265c988 100644 --- a/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin.m +++ b/packages/firebase_performance/ios/Classes/FirebasePerformancePlugin.m @@ -2,21 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "FirebasePerformancePlugin.h" - -#import - -@interface FLTFirebasePerformancePlugin () -@property(nonatomic, retain) NSMutableDictionary *traces; -@property(nonatomic, retain) NSMutableDictionary *httpMetrics; -@end +#import "FirebasePerformancePlugin+Internal.h" @implementation FLTFirebasePerformancePlugin +static NSMutableDictionary> *methodHandlers; + + (void)registerWithRegistrar:(NSObject *)registrar { + methodHandlers = [NSMutableDictionary new]; + FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/firebase_performance" binaryMessenger:[registrar messenger]]; - FLTFirebasePerformancePlugin *instance = [[FLTFirebasePerformancePlugin alloc] init]; + + FLTFirebasePerformancePlugin *instance = [FLTFirebasePerformancePlugin new]; [registrar addMethodCallDelegate:instance channel:channel]; } @@ -28,133 +26,40 @@ - (instancetype)init { [FIRApp configure]; NSLog(@"Configured the default Firebase app %@.", [FIRApp defaultApp].name); } - - _traces = [[NSMutableDictionary alloc] init]; - _httpMetrics = [[NSMutableDictionary alloc] init]; } return self; } - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { - if ([@"FirebasePerformance#isPerformanceCollectionEnabled" isEqualToString:call.method]) { - result(@([[FIRPerformance sharedInstance] isDataCollectionEnabled])); - } else if ([@"FirebasePerformance#setPerformanceCollectionEnabled" isEqualToString:call.method]) { - NSNumber *enable = call.arguments; - [[FIRPerformance sharedInstance] setDataCollectionEnabled:[enable boolValue]]; + if ([@"FirebasePerformance#instance" isEqualToString:call.method]) { + NSNumber *handle = call.arguments[@"handle"]; + FLTFirebasePerformance *performance = [FLTFirebasePerformance sharedInstance]; + + [FLTFirebasePerformancePlugin addMethodHandler:handle methodHandler:performance]; result(nil); - } else if ([@"Trace#start" isEqualToString:call.method]) { - [self handleTraceStart:call result:result]; - } else if ([@"Trace#stop" isEqualToString:call.method]) { - [self handleTraceStop:call result:result]; - } else if ([@"HttpMetric#start" isEqualToString:call.method]) { - [self handleHttpMetricStart:call result:result]; - } else if ([@"HttpMetric#stop" isEqualToString:call.method]) { - [self handleHttpMetricStop:call result:result]; } else { - result(FlutterMethodNotImplemented); - } -} - -- (void)handleTraceStart:(FlutterMethodCall *)call result:(FlutterResult)result { - NSNumber *handle = call.arguments[@"handle"]; - NSString *name = call.arguments[@"name"]; - - FIRTrace *trace = [[FIRPerformance sharedInstance] traceWithName:name]; - [_traces setObject:trace forKey:handle]; - [trace start]; - result(nil); -} - -- (void)handleTraceStop:(FlutterMethodCall *)call result:(FlutterResult)result { - NSNumber *handle = call.arguments[@"handle"]; - FIRTrace *trace = [_traces objectForKey:handle]; - - NSDictionary *metrics = call.arguments[@"metrics"]; - [metrics enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *value, BOOL *stop) { - [trace setIntValue:[value longLongValue] forMetric:key]; - }]; + NSNumber *handle = call.arguments[@"handle"]; - NSDictionary *attributes = call.arguments[@"attributes"]; - [attributes enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { - [trace setValue:value forAttribute:key]; - }]; - - [trace stop]; - [_traces removeObjectForKey:handle]; - result(nil); + if (![handle isEqual:[NSNull null]]) { + [methodHandlers[handle] handleMethodCall:call result:result]; + } else { + result(FlutterMethodNotImplemented); + } + } } -- (void)handleHttpMetricStart:(FlutterMethodCall *)call result:(FlutterResult)result { - NSNumber *handle = call.arguments[@"handle"]; - NSURL *url = [NSURL URLWithString:call.arguments[@"url"]]; - - NSNumber *httpMethod = call.arguments[@"httpMethod"]; - FIRHTTPMethod method; - switch ([httpMethod intValue]) { - case 0: - method = FIRHTTPMethodCONNECT; - break; - case 1: - method = FIRHTTPMethodDELETE; - break; - case 2: - method = FIRHTTPMethodGET; - break; - case 3: - method = FIRHTTPMethodHEAD; - break; - case 4: - method = FIRHTTPMethodOPTIONS; - break; - case 5: - method = FIRHTTPMethodPATCH; - break; - case 6: - method = FIRHTTPMethodPOST; - break; - case 7: - method = FIRHTTPMethodPUT; - break; - case 8: - method = FIRHTTPMethodTRACE; - break; - default: - method = [httpMethod intValue]; - break; ++ (void)addMethodHandler:(NSNumber *)handle methodHandler:(id)handler { + if (methodHandlers[handle]) { + NSString *reason = + [[NSString alloc] initWithFormat:@"Object for handle already exists: %d", handle.intValue]; + @throw [[NSException alloc] initWithName:NSInvalidArgumentException reason:reason userInfo:nil]; } - FIRHTTPMetric *metric = [[FIRHTTPMetric alloc] initWithURL:url HTTPMethod:method]; - [_httpMetrics setObject:metric forKey:handle]; - [metric start]; - result(nil); + methodHandlers[handle] = handler; } -- (void)handleHttpMetricStop:(FlutterMethodCall *)call result:(FlutterResult)result { - NSNumber *handle = call.arguments[@"handle"]; - FIRHTTPMetric *metric = [_httpMetrics objectForKey:handle]; - - NSNumber *responseCode = call.arguments[@"httpResponseCode"]; - NSNumber *requestPayloadSize = call.arguments[@"requestPayloadSize"]; - NSString *responseContentType = call.arguments[@"responseContentType"]; - NSNumber *responsePayloadSize = call.arguments[@"responsePayloadSize"]; - - if (![responseCode isEqual:[NSNull null]]) metric.responseCode = [responseCode integerValue]; - if (![requestPayloadSize isEqual:[NSNull null]]) - metric.requestPayloadSize = [requestPayloadSize longValue]; - if (![responseContentType isEqual:[NSNull null]]) - metric.responseContentType = responseContentType; - if (![responsePayloadSize isEqual:[NSNull null]]) - metric.responsePayloadSize = [responsePayloadSize longValue]; - - NSDictionary *attributes = call.arguments[@"attributes"]; - [attributes enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { - [metric setValue:value forAttribute:key]; - }]; - - [metric stop]; - [_httpMetrics removeObjectForKey:handle]; - result(nil); ++ (void)removeMethodHandler:(NSNumber *)handle { + [methodHandlers removeObjectForKey:handle]; } - @end