diff --git a/docs/performance/overview.mdx b/docs/performance/overview.mdx index d23aa492e757..cdb078c21f71 100644 --- a/docs/performance/overview.mdx +++ b/docs/performance/overview.mdx @@ -114,8 +114,4 @@ $ flutter run ## Next Steps -Once installed, you're ready to start using Performance Monitoring in your Flutter Project. - -> Additional documentation will be available once the Firebase Performance Monitoring plugin update lands as part of the [FlutterFire roadmap](https://github.com/FirebaseExtended/flutterfire/issues/2582). - - +Once installed, head over to the [Usage](./usage.mdx) documentation to learn more. diff --git a/docs/performance/usage.mdx b/docs/performance/usage.mdx index 742beaeef5f8..4580f95a884c 100644 --- a/docs/performance/usage.mdx +++ b/docs/performance/usage.mdx @@ -3,4 +3,125 @@ title: Performance Monitoring sidebar_label: Usage --- -Performance Monitoring usage +To start using Performance Monitoring for Firebase package within your project, import it at the top of your project files: + +```dart +import 'package:firebase_performance/firebase_performance.dart'; +``` + +Before using Performance Monitoring, you must first have ensured you have [initialized FlutterFire](overview#initializing-flutterfire). + +To create a new Performance Monitoring for Firebase instance, call the [`instance`](!firebase_performance.FirebasePerformance.instance) getter on [`FirebaseFunctions`](!firebase_performance.FirebasePerformance): + +```dart +FirebasePerformance performance = FirebasePerformance.instance; +``` + +By default, this allows you to interact with Performance Monitoring using the default Firebase App. + +## Automatic Tracing + +When installed, Android & iOS will automatically report metrics such as application start time, network requests and other useful data. + +## Custom tracing + +You can create your own traces to monitor performance data associated with specific code in your app. With a custom code trace, you can +measure how long it takes your app to complete a specific task or a set of tasks, for example loading a set of images or querying your database. + +To setup a custom trace, create a new `Trace` instance by calling the `newTrace()` method: + +```dart +FirebasePerformance performance = FirebasePerformance.instance; + +Trace trace = performance.newTrace('custom-trace'); +``` + +The name provided will appear within the Firebase Console, allowing you to provide unique names for different trace metrics. + +When it makes sense to start a trace, call the `start()` method on the `Trace` instance. Once started, you can apply custom metric +data to the trace by calling `setMetric()`: + +```dart +Trace trace = performance.newTrace('custom-trace'); + +await trace.start(); + +// Set metrics you wish to track +trace.setMetric('sum', 200); +trace.setMetric('time', 342340435); +``` + +The API also allows you to increment metric data: + +```dart +trace.setMetric('sum', 200); + +// `sum` will be incremented to 201 +trace.incrementMetric('sum', 1); +``` + +You can also set non-metric related data, such as a user id on the trace by calling the `putAttribute()` method. Note, +each trace supports up to 5 attributes. + +```dart +trace.putAttribute('userId', '1234'); +``` + +Once your trace has completed, call the `stop()` method. The data will then be sent to the Firebase Console: + +```dart +await trace.stop(); +``` + +## HTTP Request Tracing + +The network request traces automatically collected by Performance Monitoring include most network requests for your app. + +Some requests might not be reported or you might use a different library to make network requests. In these cases, +you can use the Performance Monitoring API to manually instrument custom network request traces. Custom network request traces +are only supported for Apple and Android platforms. To setup a custom network request trace, see below: + +```dart +// Using http package to make a network request +import 'package:http/http.dart' as http; + +FirebasePerformance performance = FirebasePerformance.instance; + +// Create a `HttpMetric` instance using the URL you're requesting as well as the type of request +String url = 'https://firebase.flutter.dev'; +HttpMetric metric = performance + .newHttpMetric(url, HttpMethod.Get); + +// You may also assign up to 5 attributes for each trace +metric.putAttribute('foo', 'bar'); + +// Start the trace +await metric.start(); + +// Make the request +Uri url = Uri.parse(url); +var response = await http.get(url); + +// Set specific headers to be collated +metric.responseContentType = response.headers['Content-Type']; +metric.httpResponseCode = response.statusCode; +metric.responsePayloadSize = response.contentLength; + +// Stops the trace. This is when the data is sent to the Firebase server and it will appear in your Firebase console +await metric.stop(); + +``` + +## Stop Automatic Data Collection + +To stop automatic data collection, you can call `setPerformanceCollectionEnabled` like in the example: + +```dart +FirebasePerformance performance = FirebasePerformance.instance; + +// Custom data collection is, by default, enabled +bool isEnabled = await performance.isPerformanceCollectionEnabled(); + +// Set data collection to `false` +await performance.setPerformanceCollectionEnabled(false); +``` diff --git a/docs/sidebars.js b/docs/sidebars.js index 2f1e15c3e064..c79ebf1f3c6c 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -143,6 +143,7 @@ module.exports = { ], "Performance Monitoring": [ "performance/overview", + "performance/usage", toReferenceAPI("firebase_performance"), toGithubExample("firebase_performance"), ], diff --git a/packages/firebase_performance/firebase_performance/CHANGELOG.md b/packages/firebase_performance/firebase_performance/CHANGELOG.md index 72fa3725bc8e..51bbf99b7091 100644 --- a/packages/firebase_performance/firebase_performance/CHANGELOG.md +++ b/packages/firebase_performance/firebase_performance/CHANGELOG.md @@ -1,3 +1,26 @@ +## UNRELEASED + +The Firebase Performance plugin has been heavily reworked to bring it inline with the federated plugin setup along with adding new features, +documentation and updating unit and end-to-end tests. + +- General + - Collecting metric and attribute data was previously an asynchronous task. The API has been reworked to better reflect other Firebase APIs, whereby + setting such data is now a synchronous task. Once a trace or HTTP metric stops, data will be sent to the Firebase services in a single operation. + Because of this, breaking changes are introduced to support the new API. + +- **`FirebasePerformance`** + - **BREAKING**: `HttpMetric().putAttribute()` method is now synchronous. + - **BREAKING**: `HttpMetric().removeAttribute()` method is now synchronous. + - **BREAKING**: `HttpMetric().getAttribute()` method is now synchronous. + - **BREAKING**: `HttpMetric().getAttributes()` method is now synchronous. + - **BREAKING**: `Trace().putAttribute()` method is now synchronous. + - **BREAKING**: `Trace().removeAttribute()` method is now synchronous. + - **BREAKING**: `Trace().getAttribute()` method is now synchronous. + - **BREAKING**: `Trace().getAttributes()` method is now synchronous. + - **BREAKING**: `Trace().incrementMetric()` method is now synchronous. + - **BREAKING**: `Trace().setMetric()` method is now synchronous. + - **BREAKING**: `Trace().getMetric()` method is now synchronous. + ## 0.7.1+5 - Update a dependency to the latest release. diff --git a/packages/firebase_performance/firebase_performance/README.md b/packages/firebase_performance/firebase_performance/README.md index 93ba8382c7c4..1393246eb28f 100644 --- a/packages/firebase_performance/firebase_performance/README.md +++ b/packages/firebase_performance/firebase_performance/README.md @@ -1,113 +1,25 @@ -# Google Performance Monitoring for Firebase +# Firebase Performance Plugin for Flutter -[![pub package](https://img.shields.io/pub/v/firebase_performance.svg)](https://pub.dev/packages/firebase_performance) - -A Flutter plugin to use the [Google Performance Monitoring for Firebase API](https://firebase.google.com/docs/perf-mon/). - -For Flutter plugins for other Firebase products, see [README.md](https://github.com/FirebaseExtended/flutterfire/blob/master/README.md). - -## Usage - -To use this plugin, first connect to Firebase by following the instructions for [Android](https://firebase.flutter.dev/docs/installation/android) / [iOS](https://firebase.flutter.dev/docs/installation/ios) / [Web](https://firebase.flutter.dev/docs/installation/web). Then add this plugin by following [these instructions](https://firebase.flutter.dev/docs/performance/overview). See [`example/lib/main.dart`](example/lib/main.dart) for details on the API usage. - -You can confirm that Performance Monitoring results appear in the [Firebase Performance Monitoring console](https://console.firebase.google.com/project/_/performance). Results should appear within a few minutes. - -> :warning: **Note:** *First Paint* and *First Contentful Paint* metrics of web page load trace will not be collected; automatic network request traces and screen traces will not always be collected for mobile apps. - - -### Define a Custom Trace - -A custom trace is a report of performance data associated with some of the code in your app. To learn more about custom traces, see the [Performance Monitoring overview](https://firebase.google.com/docs/perf-mon/custom-code-traces). - -```dart -final Trace myTrace = FirebasePerformance.instance.newTrace("test_trace"); -await myTrace.start(); - -final Item item = cache.fetch("item"); -if (item != null) { - await myTrace.incrementMetric("item_cache_hit", 1); -} else { - await myTrace.incrementMetric("item_cache_miss", 1); -} - -await myTrace.stop(); -``` +A Flutter plugin to use the [Firebase Performance API](https://firebase.google.com/docs/perf-mon/). -### Add monitoring for specific network requests (mobile only) +To learn more about Firebase Performance, please visit the [Firebase website](https://firebase.google.com/products/performance) -Performance Monitoring collects network requests automatically. Although this includes most network requests for your app, some might not be reported. To include specific network requests in Performance Monitoring, add the following code to your app: - -```dart -class _MetricHttpClient extends BaseClient { - _MetricHttpClient(this._inner); - - final Client _inner; - - @override - Future send(BaseRequest request) async { - final HttpMetric metric = FirebasePerformance.instance - .newHttpMetric(request.url.toString(), HttpMethod.Get); - - await metric.start(); - - StreamedResponse response; - try { - response = await _inner.send(request); - metric - ..responsePayloadSize = response.contentLength - ..responseContentType = response.headers['Content-Type'] - ..requestPayloadSize = request.contentLength - ..httpResponseCode = response.statusCode; - } finally { - await metric.stop(); - } +[![pub package](https://img.shields.io/pub/v/firebase_performance.svg)](https://pub.dev/packages/firebase_performance) - return response; - } -} +## Getting Started -class _MyAppState extends State { -. -. -. - Future testHttpMetric() async { - final _MetricHttpClient metricHttpClient = _MetricHttpClient(Client()); +To get started with Firebase Performance for Flutter, please [see the documentation](https://firebase.flutter.dev/docs/performance/overview). - final Request request = - Request("SEND", Uri.parse("https://www.google.com")); +## Usage - metricHttpClient.send(request); - } -. -. -. -} -``` +To use this plugin, please visit the [Firebase Performance Usage documentation](https://firebase.flutter.dev/docs/performance/usage) ## Issues and feedback Please file FlutterFire specific issues, bugs, or feature requests in our [issue tracker](https://github.com/FirebaseExtended/flutterfire/issues/new). -Plugin issues that are not specific to Flutterfire can be filed in the [Flutter issue tracker](https://github.com/flutter/flutter/issues/new). - -## Contribution +Plugin issues that are not specific to FlutterFire can be filed in the [Flutter issue tracker](https://github.com/flutter/flutter/issues/new). To contribute a change to this plugin, please review our [contribution guide](https://github.com/FirebaseExtended/flutterfire/blob/master/CONTRIBUTING.md) and open a [pull request](https://github.com/FirebaseExtended/flutterfire/pulls). - -### Testing - -The unit test is in `test` directory which you can run using `flutter test`. - -The integration test is in `example/test_driver/firebase_performance_e2e.dart` which you can run on an emulator: -``` -cd example -flutter drive --target=./test_driver/firebase_performance_e2e.dart -``` - -To test the web implementation, [download and run ChromeDriver](https://flutter.dev/docs/testing/integration-tests#running-in-a-browser), and then run `flutter_drive`: - -``` -flutter drive --target=./test_driver/firebase_performance_e2e.dart -d web-server --release --browser-name=chrome --web-port=8080 -``` diff --git a/packages/firebase_performance/firebase_performance/android/build.gradle b/packages/firebase_performance/firebase_performance/android/build.gradle index fd959d089bea..68a7d64bd987 100644 --- a/packages/firebase_performance/firebase_performance/android/build.gradle +++ b/packages/firebase_performance/firebase_performance/android/build.gradle @@ -50,6 +50,10 @@ android { implementation 'com.google.firebase:firebase-perf' implementation 'androidx.annotation:annotation:1.1.0' } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } diff --git a/packages/firebase_performance/firebase_performance/android/src/main/AndroidManifest.xml b/packages/firebase_performance/firebase_performance/android/src/main/AndroidManifest.xml index 2d19cbfdc23e..726d14ecf2dc 100644 --- a/packages/firebase_performance/firebase_performance/android/src/main/AndroidManifest.xml +++ b/packages/firebase_performance/firebase_performance/android/src/main/AndroidManifest.xml @@ -1,10 +1,10 @@ + package="io.flutter.plugins.firebase.performance"> - diff --git a/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterFirebaseAppRegistrar.java b/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebase/performance/FlutterFirebaseAppRegistrar.java similarity index 85% rename from packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterFirebaseAppRegistrar.java rename to packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebase/performance/FlutterFirebaseAppRegistrar.java index 6d9a57c3f492..56b2acb97622 100644 --- a/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterFirebaseAppRegistrar.java +++ b/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebase/performance/FlutterFirebaseAppRegistrar.java @@ -1,8 +1,8 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. +// Copyright 2021 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. -package io.flutter.plugins.firebaseperformance; +package io.flutter.plugins.firebase.performance; import androidx.annotation.Keep; import com.google.firebase.components.Component; diff --git a/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterFirebasePerformance.java b/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebase/performance/FlutterFirebasePerformance.java similarity index 95% rename from packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterFirebasePerformance.java rename to packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebase/performance/FlutterFirebasePerformance.java index 7c786ae3f3fc..a2522a420b6a 100644 --- a/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterFirebasePerformance.java +++ b/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebase/performance/FlutterFirebasePerformance.java @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -package io.flutter.plugins.firebaseperformance; +package io.flutter.plugins.firebase.performance; import com.google.firebase.perf.FirebasePerformance; import com.google.firebase.perf.metrics.HttpMetric; @@ -36,10 +36,10 @@ private static String parseHttpMethod(String httpMethod) { } } - private final FirebasePerformancePlugin plugin; + private final FlutterFirebasePerformancePlugin plugin; private final FirebasePerformance performance; - FlutterFirebasePerformance(FirebasePerformancePlugin plugin) { + FlutterFirebasePerformance(FlutterFirebasePerformancePlugin plugin) { this.plugin = plugin; this.performance = FirebasePerformance.getInstance(); } diff --git a/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FirebasePerformancePlugin.java b/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebase/performance/FlutterFirebasePerformancePlugin.java similarity index 55% rename from packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FirebasePerformancePlugin.java rename to packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebase/performance/FlutterFirebasePerformancePlugin.java index 31a76ae9ca02..10d43b914643 100644 --- a/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FirebasePerformancePlugin.java +++ b/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebase/performance/FlutterFirebasePerformancePlugin.java @@ -1,45 +1,51 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2021 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. -package io.flutter.plugins.firebaseperformance; +package io.flutter.plugins.firebase.performance; + +import static io.flutter.plugins.firebase.core.FlutterFirebasePluginRegistry.registerPlugin; import android.util.SparseArray; +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.Tasks; +import com.google.firebase.FirebaseApp; import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import io.flutter.plugins.firebase.core.FlutterFirebasePlugin; +import java.util.HashMap; +import java.util.Map; /** * Flutter plugin accessing Firebase Performance API. * *

Instantiate this in an add to app scenario to gracefully handle activity and context changes. */ -public class FirebasePerformancePlugin implements FlutterPlugin, MethodChannel.MethodCallHandler { - private static final String CHANNEL_NAME = "plugins.flutter.io/firebase_performance"; +public class FlutterFirebasePerformancePlugin + implements FlutterFirebasePlugin, FlutterPlugin, MethodCallHandler { + private static final String METHOD_CHANNEL_NAME = "plugins.flutter.io/firebase_performance"; - private final SparseArray handlers = new SparseArray<>(); + private final SparseArray handlers = new SparseArray<>(); private MethodChannel channel; - @Override - public void onAttachedToEngine(FlutterPluginBinding binding) { - channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME); + private void initInstance(BinaryMessenger messenger) { + registerPlugin(METHOD_CHANNEL_NAME, this); + channel = new MethodChannel(messenger, METHOD_CHANNEL_NAME); channel.setMethodCallHandler(this); } @Override - public void onDetachedFromEngine(FlutterPluginBinding binding) { - channel.setMethodCallHandler(null); + public void onAttachedToEngine(FlutterPluginBinding binding) { + initInstance(binding.getBinaryMessenger()); } @Override - public void onMethodCall(MethodCall call, MethodChannel.Result result) { - if (getHandler(call) == null) { - final Integer handle = call.argument("handle"); - addHandler(handle, new FlutterFirebasePerformance(this)); - } - - final MethodChannel.MethodCallHandler handler = getHandler(call); - handler.onMethodCall(call, result); + public void onDetachedFromEngine(FlutterPluginBinding binding) { + channel.setMethodCallHandler(null); + channel = null; } void addHandler(final int handle, final MethodChannel.MethodCallHandler handler) { @@ -61,4 +67,25 @@ private MethodChannel.MethodCallHandler getHandler(final MethodCall call) { if (handle == null) return null; return handlers.get(handle); } + + @Override + public void onMethodCall(MethodCall call, MethodChannel.Result result) { + if (getHandler(call) == null) { + final Integer handle = call.argument("handle"); + addHandler(handle, new FlutterFirebasePerformance(this)); + } + + final MethodChannel.MethodCallHandler handler = getHandler(call); + handler.onMethodCall(call, result); + } + + @Override + public Task> getPluginConstantsForFirebaseApp(FirebaseApp firebaseApp) { + return Tasks.call(() -> new HashMap() {}); + } + + @Override + public Task didReinitializeFirebaseCore() { + return Tasks.call(() -> null); + } } diff --git a/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebase/performance/FlutterHttpMetric.java b/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebase/performance/FlutterHttpMetric.java new file mode 100644 index 000000000000..5a8a38f2e6e2 --- /dev/null +++ b/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebase/performance/FlutterHttpMetric.java @@ -0,0 +1,75 @@ +// 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. + +package io.flutter.plugins.firebase.performance; + +import com.google.firebase.perf.metrics.HttpMetric; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import java.util.Map; +import java.util.Objects; + +class FlutterHttpMetric implements MethodChannel.MethodCallHandler { + private final FlutterFirebasePerformancePlugin plugin; + private final HttpMetric httpMetric; + + FlutterHttpMetric(FlutterFirebasePerformancePlugin plugin, final HttpMetric metric) { + this.plugin = plugin; + this.httpMetric = metric; + } + + @Override + public void onMethodCall(MethodCall call, MethodChannel.Result result) { + switch (call.method) { + case "HttpMetric#start": + start(result); + break; + case "HttpMetric#stop": + stop(call, result); + break; + default: + result.notImplemented(); + } + } + + private void start(MethodChannel.Result result) { + httpMetric.start(); + result.success(null); + } + + @SuppressWarnings("ConstantConditions") + private void stop(MethodCall call, MethodChannel.Result result) { + final Map attributes = Objects.requireNonNull((call.argument("attributes"))); + final Integer httpResponseCode = call.argument("httpResponseCode"); + final Integer requestPayloadSize = call.argument("requestPayloadSize"); + final String responseContentType = call.argument("responseContentType"); + final Integer responsePayloadSize = call.argument("responsePayloadSize"); + + if (httpResponseCode != null) { + httpMetric.setHttpResponseCode(httpResponseCode); + } + if (requestPayloadSize != null) { + httpMetric.setRequestPayloadSize(requestPayloadSize); + } + if (responseContentType != null) { + httpMetric.setResponseContentType(responseContentType); + } + if (responsePayloadSize != null) { + httpMetric.setRequestPayloadSize(responsePayloadSize); + } + + for (String key : attributes.keySet()) { + String attributeValue = (String) attributes.get(key); + + httpMetric.putAttribute(key, attributeValue); + } + + httpMetric.stop(); + + final Integer handle = call.argument("handle"); + plugin.removeHandler(handle); + + result.success(null); + } +} diff --git a/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebase/performance/FlutterTrace.java b/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebase/performance/FlutterTrace.java new file mode 100644 index 000000000000..6dcb6e55f001 --- /dev/null +++ b/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebase/performance/FlutterTrace.java @@ -0,0 +1,65 @@ +// 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. + +package io.flutter.plugins.firebase.performance; + +import com.google.firebase.perf.metrics.Trace; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import java.util.Map; +import java.util.Objects; + +class FlutterTrace implements MethodChannel.MethodCallHandler { + private final FlutterFirebasePerformancePlugin plugin; + private final Trace trace; + + FlutterTrace(FlutterFirebasePerformancePlugin plugin, final Trace trace) { + this.plugin = plugin; + this.trace = trace; + } + + @Override + public void onMethodCall(MethodCall call, MethodChannel.Result result) { + switch (call.method) { + case "Trace#start": + start(result); + break; + case "Trace#stop": + stop(call, result); + break; + default: + result.notImplemented(); + } + } + + private void start(MethodChannel.Result result) { + trace.start(); + result.success(null); + } + + @SuppressWarnings("ConstantConditions") + private void stop(MethodCall call, MethodChannel.Result result) { + final Map attributes = Objects.requireNonNull((call.argument("attributes"))); + final Map metrics = Objects.requireNonNull((call.argument("metrics"))); + + for (String key : attributes.keySet()) { + String attributeValue = (String) attributes.get(key); + + trace.putAttribute(key, attributeValue); + } + + for (String key : metrics.keySet()) { + Integer metricValue = (Integer) metrics.get(key); + + trace.putMetric(key, metricValue); + } + + trace.stop(); + + final Integer handle = call.argument("handle"); + plugin.removeHandler(handle); + + result.success(null); + } +} diff --git a/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterHttpMetric.java b/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterHttpMetric.java deleted file mode 100644 index 7aca2294f8d1..000000000000 --- a/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterHttpMetric.java +++ /dev/null @@ -1,118 +0,0 @@ -// 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. - -package io.flutter.plugins.firebaseperformance; - -import com.google.firebase.perf.metrics.HttpMetric; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; - -class FlutterHttpMetric implements MethodChannel.MethodCallHandler { - private final FirebasePerformancePlugin plugin; - private final HttpMetric httpMetric; - - FlutterHttpMetric(FirebasePerformancePlugin plugin, final HttpMetric metric) { - this.plugin = plugin; - this.httpMetric = metric; - } - - @Override - public void onMethodCall(MethodCall call, MethodChannel.Result result) { - switch (call.method) { - case "HttpMetric#start": - start(result); - break; - case "HttpMetric#stop": - stop(call, result); - break; - case "HttpMetric#httpResponseCode": - setHttpResponseCode(call, result); - break; - case "HttpMetric#requestPayloadSize": - setRequestPayloadSize(call, result); - break; - case "HttpMetric#responseContentType": - setResponseContentType(call, result); - break; - case "HttpMetric#responsePayloadSize": - setResponsePayloadSize(call, result); - break; - case "HttpMetric#putAttribute": - putAttribute(call, result); - break; - case "HttpMetric#removeAttribute": - removeAttribute(call, result); - break; - case "HttpMetric#getAttributes": - getAttributes(result); - break; - default: - result.notImplemented(); - } - } - - private void start(MethodChannel.Result result) { - httpMetric.start(); - result.success(null); - } - - @SuppressWarnings("ConstantConditions") - private void stop(MethodCall call, MethodChannel.Result result) { - httpMetric.stop(); - - final Integer handle = call.argument("handle"); - plugin.removeHandler(handle); - - result.success(null); - } - - @SuppressWarnings("ConstantConditions") - private void setHttpResponseCode(MethodCall call, MethodChannel.Result result) { - final Integer httpResponseCode = call.argument("httpResponseCode"); - httpMetric.setHttpResponseCode(httpResponseCode); - result.success(null); - } - - @SuppressWarnings("ConstantConditions") - private void setRequestPayloadSize(MethodCall call, MethodChannel.Result result) { - final Number payloadSize = call.argument("requestPayloadSize"); - httpMetric.setRequestPayloadSize(payloadSize != null ? payloadSize.longValue() : null); - result.success(null); - } - - private void setResponseContentType(MethodCall call, MethodChannel.Result result) { - final String contentType = call.argument("responseContentType"); - httpMetric.setResponseContentType(contentType); - result.success(null); - } - - @SuppressWarnings("ConstantConditions") - private void setResponsePayloadSize(MethodCall call, MethodChannel.Result result) { - final Number payloadSize = call.argument("responsePayloadSize"); - httpMetric.setResponsePayloadSize(payloadSize != null ? payloadSize.longValue() : null); - result.success(null); - } - - @SuppressWarnings("ConstantConditions") - private void putAttribute(MethodCall call, MethodChannel.Result result) { - final String name = call.argument("name"); - final String value = call.argument("value"); - - httpMetric.putAttribute(name, value); - - result.success(null); - } - - @SuppressWarnings("ConstantConditions") - private void removeAttribute(MethodCall call, MethodChannel.Result result) { - final String name = call.argument("name"); - httpMetric.removeAttribute(name); - - result.success(null); - } - - private void getAttributes(MethodChannel.Result result) { - result.success(httpMetric.getAttributes()); - } -} diff --git a/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterTrace.java b/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterTrace.java deleted file mode 100644 index 65ce0bcc5df4..000000000000 --- a/packages/firebase_performance/firebase_performance/android/src/main/java/io/flutter/plugins/firebaseperformance/FlutterTrace.java +++ /dev/null @@ -1,113 +0,0 @@ -// 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. - -package io.flutter.plugins.firebaseperformance; - -import com.google.firebase.perf.metrics.Trace; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; - -class FlutterTrace implements MethodChannel.MethodCallHandler { - private final FirebasePerformancePlugin plugin; - private final Trace trace; - - FlutterTrace(FirebasePerformancePlugin plugin, final Trace trace) { - this.plugin = plugin; - this.trace = trace; - } - - @Override - public void onMethodCall(MethodCall call, MethodChannel.Result result) { - switch (call.method) { - case "Trace#start": - start(result); - break; - case "Trace#stop": - stop(call, result); - break; - case "Trace#setMetric": - setMetric(call, result); - break; - case "Trace#incrementMetric": - incrementMetric(call, result); - break; - case "Trace#getMetric": - getMetric(call, result); - break; - case "Trace#putAttribute": - putAttribute(call, result); - break; - case "Trace#removeAttribute": - removeAttribute(call, result); - break; - case "Trace#getAttributes": - getAttributes(result); - break; - default: - result.notImplemented(); - } - } - - private void start(MethodChannel.Result result) { - trace.start(); - result.success(null); - } - - @SuppressWarnings("ConstantConditions") - private void stop(MethodCall call, MethodChannel.Result result) { - trace.stop(); - - final Integer handle = call.argument("handle"); - plugin.removeHandler(handle); - - result.success(null); - } - - @SuppressWarnings("ConstantConditions") - private void setMetric(MethodCall call, MethodChannel.Result result) { - final String name = call.argument("name"); - final Number value = call.argument("value"); - trace.putMetric(name, value.longValue()); - - result.success(null); - } - - @SuppressWarnings("ConstantConditions") - private void incrementMetric(MethodCall call, MethodChannel.Result result) { - final String name = call.argument("name"); - final Number value = call.argument("value"); - trace.incrementMetric(name, value.longValue()); - - result.success(null); - } - - @SuppressWarnings("ConstantConditions") - private void getMetric(MethodCall call, MethodChannel.Result result) { - final String name = call.argument("name"); - - result.success(trace.getLongMetric(name)); - } - - @SuppressWarnings("ConstantConditions") - private void putAttribute(MethodCall call, MethodChannel.Result result) { - final String name = call.argument("name"); - final String value = call.argument("value"); - - trace.putAttribute(name, value); - - result.success(null); - } - - @SuppressWarnings("ConstantConditions") - private void removeAttribute(MethodCall call, MethodChannel.Result result) { - final String name = call.argument("name"); - trace.removeAttribute(name); - - result.success(null); - } - - private void getAttributes(MethodChannel.Result result) { - result.success(trace.getAttributes()); - } -} diff --git a/packages/firebase_performance/firebase_performance/example/android/app/build.gradle b/packages/firebase_performance/firebase_performance/example/android/app/build.gradle index 4c992e59a154..1a5d087006e5 100644 --- a/packages/firebase_performance/firebase_performance/example/android/app/build.gradle +++ b/packages/firebase_performance/firebase_performance/example/android/app/build.gradle @@ -1,24 +1,24 @@ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { - flutterVersionCode = '1' + flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { - flutterVersionName = '1.0' + flutterVersionName = '1.0' } apply plugin: 'com.android.application' @@ -26,36 +26,40 @@ apply plugin: "com.google.firebase.firebase-perf" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 29 + compileSdkVersion 31 - lintOptions { - disable 'InvalidPackage' - } + lintOptions { + disable 'InvalidPackage' + } - defaultConfig { - applicationId "io.flutter.plugins.firebaseperformanceexample" - minSdkVersion 21 - targetSdkVersion 28 - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } + defaultConfig { + applicationId "io.flutter.plugins.firebaseperformanceexample" + minSdkVersion 21 + targetSdkVersion 31 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } - buildTypes { - release { - signingConfig signingConfigs.debug - } + buildTypes { + release { + signingConfig signingConfigs.debug } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } flutter { - source '../..' + source '../..' } dependencies { - androidTestImplementation 'androidx.test:runner:1.2.0' - androidTestImplementation 'androidx.test:rules:1.2.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test:rules:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' } -apply plugin: 'com.google.gms.google-services' \ No newline at end of file +apply plugin: 'com.google.gms.google-services' diff --git a/packages/firebase_performance/firebase_performance/example/ios/Flutter/AppFrameworkInfo.plist b/packages/firebase_performance/firebase_performance/example/ios/Flutter/AppFrameworkInfo.plist index 6c2de8086bcd..3a9c234f96d4 100644 --- a/packages/firebase_performance/firebase_performance/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/firebase_performance/firebase_performance/example/ios/Flutter/AppFrameworkInfo.plist @@ -25,6 +25,6 @@ arm64 MinimumOSVersion - 8.0 + 9.0 diff --git a/packages/firebase_performance/firebase_performance/example/ios/Runner.xcodeproj/project.pbxproj b/packages/firebase_performance/firebase_performance/example/ios/Runner.xcodeproj/project.pbxproj index 9a568387a864..376168acff46 100644 --- a/packages/firebase_performance/firebase_performance/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/firebase_performance/firebase_performance/example/ios/Runner.xcodeproj/project.pbxproj @@ -8,14 +8,8 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 4F30D5F713FAE573DBE831BD /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AADA30890C75F448DA1E1040 /* libPods-Runner.a */; }; - 8FF7AD1520880821009B43A2 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 8FF7AD1420880820009B43A2 /* GoogleService-Info.plist */; }; - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; @@ -32,8 +26,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -43,16 +35,14 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; + 355BC2C50462780197DDCB5E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 73333FD263564BF21EA2CA68 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 8FF7AD1420880820009B43A2 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -67,8 +57,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 4F30D5F713FAE573DBE831BD /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -87,10 +75,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 2D5378251FAA1A9400D5DBA9 /* flutter_assets */, - 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, @@ -121,7 +106,6 @@ isa = PBXGroup; children = ( 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, - 8FF7AD1420880820009B43A2 /* GoogleService-Info.plist */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, @@ -145,6 +129,8 @@ DE7EB5CF2B4FC4A8BCD09CC6 /* Pods */ = { isa = PBXGroup; children = ( + 73333FD263564BF21EA2CA68 /* Pods-Runner.debug.xcconfig */, + 355BC2C50462780197DDCB5E /* Pods-Runner.release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -163,7 +149,6 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - EEF842937EAC4019A711CBB3 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -193,6 +178,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); @@ -212,12 +198,10 @@ buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 8FF7AD1520880821009B43A2 /* GoogleService-Info.plist in Resources */, 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -237,7 +221,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 92CA2C06BD2DACC2EF38987C /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -271,24 +255,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - EEF842937EAC4019A711CBB3 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -326,7 +292,6 @@ /* Begin XCBuildConfiguration section */ 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -380,7 +345,6 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; diff --git a/packages/firebase_performance/firebase_performance/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/firebase_performance/firebase_performance/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16ed0f..919434a6254f 100644 --- a/packages/firebase_performance/firebase_performance/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/packages/firebase_performance/firebase_performance/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/firebase_performance/firebase_performance/example/ios/Runner/GoogleService-Info.plist b/packages/firebase_performance/firebase_performance/example/ios/Runner/GoogleService-Info.plist deleted file mode 100644 index b3d4e2b0cea0..000000000000 --- a/packages/firebase_performance/firebase_performance/example/ios/Runner/GoogleService-Info.plist +++ /dev/null @@ -1,38 +0,0 @@ - - - - - CLIENT_ID - 448618578101-hqml84baqlms4dfthm77qrhdot7reesa.apps.googleusercontent.com - REVERSED_CLIENT_ID - com.googleusercontent.apps.448618578101-hqml84baqlms4dfthm77qrhdot7reesa - ANDROID_CLIENT_ID - 448618578101-2baveavh8bvs2famsa5r8t77fe1nrcn6.apps.googleusercontent.com - API_KEY - AIzaSyAHAsf51D0A407EklG1bs-5wA7EbyfNFg0 - GCM_SENDER_ID - 448618578101 - PLIST_VERSION - 1 - BUNDLE_ID - io.flutter.plugins.firebasePerformanceExample - PROJECT_ID - react-native-firebase-testing - STORAGE_BUCKET - react-native-firebase-testing.appspot.com - IS_ADS_ENABLED - - IS_ANALYTICS_ENABLED - - IS_APPINVITE_ENABLED - - IS_GCM_ENABLED - - IS_SIGNIN_ENABLED - - GOOGLE_APP_ID - 1:448618578101:ios:bfee21690b400a65ac3efc - DATABASE_URL - https://react-native-firebase-testing.firebaseio.com - - \ No newline at end of file diff --git a/packages/firebase_performance/firebase_performance/example/lib/main.dart b/packages/firebase_performance/firebase_performance/example/lib/main.dart index f74fcd5e5e74..7f5357973b8f 100644 --- a/packages/firebase_performance/firebase_performance/example/lib/main.dart +++ b/packages/firebase_performance/firebase_performance/example/lib/main.dart @@ -1,5 +1,4 @@ -// ignore_for_file: require_trailing_commas -// Copyright 2019 The Chromium Authors. All rights reserved. +// Copyright 2021 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. @@ -18,10 +17,6 @@ Future main() async { runApp(MyApp()); } -void myLog(String msg) { - print('My Log: $msg'); -} - class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); @@ -39,33 +34,33 @@ class _MetricHttpClient extends BaseClient { final HttpMetric metric = FirebasePerformance.instance .newHttpMetric(request.url.toString(), HttpMethod.Get); + metric.requestPayloadSize = request.contentLength; await metric.start(); StreamedResponse response; try { response = await _inner.send(request); - myLog( - 'Called ${request.url} with custom monitoring, response code: ${response.statusCode}'); + print( + 'Called ${request.url} with custom monitoring, response code: ${response.statusCode}', + ); - metric - ..responsePayloadSize = response.contentLength - ..responseContentType = response.headers['Content-Type'] - ..requestPayloadSize = request.contentLength - ..httpResponseCode = response.statusCode; + metric.responseContentType = 'text/html'; + metric.httpResponseCode = response.statusCode; + metric.responsePayloadSize = response.contentLength; - await metric.putAttribute('score', '15'); - await metric.putAttribute('to_be_removed', 'should_not_be_logged'); + metric.putAttribute('score', '15'); + metric.putAttribute('to_be_removed', 'should_not_be_logged'); } finally { - await metric.removeAttribute('to_be_removed'); + metric.removeAttribute('to_be_removed'); await metric.stop(); } - unawaited(metric - .getAttributes() - .then((attributes) => myLog('Http metric attributes: $attributes'))); + final attributes = metric.getAttributes(); + + print('Http metric attributes: $attributes.'); String? score = metric.getAttribute('score'); - myLog('Http metric score attribute value: $score'); + print('Http metric score attribute value: $score'); return response; } @@ -106,27 +101,25 @@ class _MyAppState extends State { _trace1HasRan = false; }); - final Trace trace = _performance.newTrace('test_trace_1'); + final Trace trace = _performance.newTrace('test_trace_3'); await trace.start(); - await trace.putAttribute('favorite_color', 'blue'); - await trace.putAttribute('to_be_removed', 'should_not_be_logged'); + trace.putAttribute('favorite_color', 'blue'); + trace.putAttribute('to_be_removed', 'should_not_be_logged'); - for (int i = 0; i < 10; i++) { - await trace.incrementMetric('sum', i); - } + trace.incrementMetric('sum', 200); + trace.incrementMetric('total', 342); - await trace.removeAttribute('to_be_removed'); + trace.removeAttribute('to_be_removed'); await trace.stop(); - unawaited(trace - .getMetric('sum') - .then((sumValue) => myLog('test_trace_1 sum value: $sumValue'))); - unawaited(trace - .getAttributes() - .then((attributes) => myLog('test_trace_1 attributes: $attributes'))); + final sum = trace.getMetric('sum'); + print('test_trace_1 sum value: $sum'); - String? favoriteColor = trace.getAttribute('favorite_color'); - myLog('test_trace_1 favorite_color: $favoriteColor'); + final attributes = trace.getAttributes(); + print('test_trace_1 attributes: $attributes'); + + final favoriteColor = trace.getAttribute('favorite_color'); + print('test_trace_1 favorite_color: $favoriteColor'); setState(() { _trace1HasRan = true; @@ -138,19 +131,15 @@ class _MyAppState extends State { _trace2HasRan = false; }); - final Trace trace = await FirebasePerformance.startTrace('test_trace_2'); + final Trace trace = FirebasePerformance.instance.newTrace('test_trace_2'); + await trace.start(); - int sum = 0; - for (int i = 0; i < 10000000; i++) { - sum += i; - } - // Trace.setMetric is no-op for web - await trace.setMetric('sum', sum); + trace.setMetric('sum', 333); + trace.setMetric('sum_2', 895); await trace.stop(); - unawaited(trace - .getMetric('sum') - .then((sumValue) => myLog('test_trace_2 sum value: $sumValue'))); + final sum2 = trace.getMetric('sum'); + print('test_trace_2 sum value: $sum2'); setState(() { _trace2HasRan = true; @@ -166,7 +155,7 @@ class _MyAppState extends State { final Request request = Request( 'SEND', - Uri.parse('https://www.google.com'), + Uri.parse('https://www.bbc.co.uk'), ); unawaited(metricHttpClient.send(request)); @@ -178,7 +167,7 @@ class _MyAppState extends State { Future _testAutomaticHttpMetric() async { Response response = await get(Uri.parse('https://www.facebook.com')); - myLog('Called facebook, response code: ${response.statusCode}'); + print('Called facebook, response code: ${response.statusCode}'); } @override diff --git a/packages/firebase_performance/firebase_performance/example/pubspec.yaml b/packages/firebase_performance/firebase_performance/example/pubspec.yaml index 285e762dd250..fb75a3139ac0 100644 --- a/packages/firebase_performance/firebase_performance/example/pubspec.yaml +++ b/packages/firebase_performance/firebase_performance/example/pubspec.yaml @@ -22,13 +22,15 @@ dependency_overrides: path: ../../../firebase_core/firebase_core_platform_interface firebase_core_web: path: ../../../firebase_core/firebase_core_web + firebase_performance: + path: ../ firebase_performance_platform_interface: path: ../../firebase_performance_platform_interface firebase_performance_web: path: ../../firebase_performance_web dev_dependencies: - e2e: ^0.6.1 + drive: 1.0.0-1.0.nullsafety.1 flutter_driver: sdk: flutter flutter_test: diff --git a/packages/firebase_performance/firebase_performance/example/test_driver/firebase_performance_e2e.dart b/packages/firebase_performance/firebase_performance/example/test_driver/firebase_performance_e2e.dart index 6ce4f9aa6972..b6429acc8ebd 100644 --- a/packages/firebase_performance/firebase_performance/example/test_driver/firebase_performance_e2e.dart +++ b/packages/firebase_performance/firebase_performance/example/test_driver/firebase_performance_e2e.dart @@ -1,77 +1,41 @@ -// ignore_for_file: require_trailing_commas -// Copyright 2020 The Chromium Authors. All rights reserved. +// Copyright 2021 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. -// @dart=2.9 - -import 'package:e2e/e2e.dart'; +import 'package:drive/drive.dart' as drive; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:firebase_performance/firebase_performance.dart'; -import 'package:pedantic/pedantic.dart'; import 'package:flutter/foundation.dart' show kIsWeb; -Future main() async { - E2EWidgetsFlutterBinding.ensureInitialized(); - +void testsMain() { setUpAll(() async { await Firebase.initializeApp(); }); - group('$FirebasePerformance', () { - testWidgets('test instance is singleton', (WidgetTester tester) async { - FirebasePerformance performance1 = FirebasePerformance.instance; - FirebasePerformance performance2 = FirebasePerformance.instance; - - expect(identical(performance1, performance2), isTrue); - }); - - testWidgets('setPerformanceCollectionEnabled', (WidgetTester tester) async { + group('$FirebasePerformance.instance', () { + test('isPerformanceCollectionEnabled', () async { FirebasePerformance performance = FirebasePerformance.instance; - await performance.setPerformanceCollectionEnabled(true); expect( performance.isPerformanceCollectionEnabled(), completion(isTrue), ); - - await performance.setPerformanceCollectionEnabled(false); - - if (kIsWeb) { - expect( - performance.isPerformanceCollectionEnabled(), - completion(isTrue), - ); - } else { - expect( - performance.isPerformanceCollectionEnabled(), - completion(isFalse), - ); - } }); - - testWidgets('test all Http method values', (WidgetTester tester) async { + test('setPerformanceCollectionEnabled', () async { FirebasePerformance performance = FirebasePerformance.instance; - for (final HttpMethod method in HttpMethod.values) { - final HttpMetric testMetric = performance.newHttpMetric( - 'https://www.google.com/', - method, - ); - unawaited(testMetric.start()); - unawaited(testMetric.stop()); - } - }); - testWidgets('startTrace', (WidgetTester tester) async { - Trace testTrace = await FirebasePerformance.startTrace('testTrace'); - await testTrace.stop(); + await performance.setPerformanceCollectionEnabled(false); + expect( + performance.isPerformanceCollectionEnabled(), + completion(isFalse), + ); }); }); group('$Trace', () { - FirebasePerformance performance; - Trace testTrace; + late FirebasePerformance performance; + late Trace testTrace; const String metricName = 'test-metric'; setUpAll(() async { @@ -79,272 +43,172 @@ Future main() async { await performance.setPerformanceCollectionEnabled(true); }); - setUp(() { + setUp(() async { testTrace = performance.newTrace('test-trace'); }); - tearDown(() { - testTrace.stop(); - testTrace = null; - }); - - testWidgets('incrementMetric does nothing before the trace is started', - (WidgetTester tester) async { - await testTrace.incrementMetric(metricName, 100); - await testTrace.start(); - - expect(testTrace.getMetric(metricName), completion(0)); - }, skip: kIsWeb); - - testWidgets( - "incrementMetric works correctly after the trace is started and before it's stopped", - (WidgetTester tester) async { - await testTrace.start(); - - await testTrace.incrementMetric(metricName, 14); - expect(testTrace.getMetric(metricName), completion(14)); - - await testTrace.incrementMetric(metricName, 45); - expect(testTrace.getMetric(metricName), completion(59)); - }); - - testWidgets('incrementMetric does nothing after the trace is stopped', - (WidgetTester tester) async { + test('start & stop trace', () async { await testTrace.start(); await testTrace.stop(); - await testTrace.incrementMetric(metricName, 100); - - expect(testTrace.getMetric(metricName), completion(0)); - }, skip: kIsWeb); + }); - testWidgets('setMetric does nothing before the trace is started', - (WidgetTester tester) async { - await testTrace.setMetric(metricName, 37); - await testTrace.start(); + test('incrementMetric works correctly', () { + testTrace.incrementMetric(metricName, 14); + expect(testTrace.getMetric(metricName), 14); - expect(testTrace.getMetric(metricName), completion(0)); + testTrace.incrementMetric(metricName, 45); + expect(testTrace.getMetric(metricName), 59); }); - testWidgets( - "setMetric works correctly after the trace is started and before it's stopped", - (WidgetTester tester) async { - await testTrace.start(); - - await testTrace.setMetric(metricName, 37); + test('setMetric works correctly', () async { + testTrace.setMetric(metricName, 37); if (kIsWeb) { - expect(testTrace.getMetric(metricName), completion(0)); + expect(testTrace.getMetric(metricName), 0); } else { - expect(testTrace.getMetric(metricName), completion(37)); + expect(testTrace.getMetric(metricName), 37); } - await testTrace.setMetric(metricName, 3); + testTrace.setMetric(metricName, 3); if (kIsWeb) { - expect(testTrace.getMetric(metricName), completion(0)); + expect(testTrace.getMetric(metricName), 0); } else { - expect(testTrace.getMetric(metricName), completion(3)); + expect(testTrace.getMetric(metricName), 3); } }); - testWidgets('setMetric does nothing after the trace is stopped', - (WidgetTester tester) async { - await testTrace.start(); - await testTrace.stop(); - await testTrace.setMetric(metricName, 100); - - expect(testTrace.getMetric(metricName), completion(0)); - }); - - testWidgets('putAttribute works correctly before the trace is stopped', - (WidgetTester tester) async { - await testTrace.putAttribute('apple', 'sauce'); - await testTrace.putAttribute('banana', 'pie'); + test('putAttribute works correctly', () { + testTrace.putAttribute('apple', 'sauce'); + testTrace.putAttribute('banana', 'pie'); expect( testTrace.getAttributes(), - completion({'apple': 'sauce', 'banana': 'pie'}), + {'apple': 'sauce', 'banana': 'pie'}, ); - await testTrace.putAttribute('apple', 'sauce2'); + testTrace.putAttribute('apple', 'sauce2'); expect( testTrace.getAttributes(), - completion({'apple': 'sauce2', 'banana': 'pie'}), + {'apple': 'sauce2', 'banana': 'pie'}, ); }); - testWidgets('putAttribute does nothing after the trace is stopped', - (WidgetTester tester) async { - await testTrace.start(); - await testTrace.stop(); - await testTrace.putAttribute('apple', 'sauce'); + test('removeAttribute works correctly', () { + testTrace.putAttribute('sponge', 'bob'); + testTrace.putAttribute('patrick', 'star'); + testTrace.removeAttribute('sponge'); expect( testTrace.getAttributes(), - completion({}), + {'patrick': 'star'}, ); - }, skip: kIsWeb); - testWidgets('removeAttribute works correctly before the trace is stopped', - (WidgetTester tester) async { - await testTrace.putAttribute('sponge', 'bob'); - await testTrace.putAttribute('patrick', 'star'); - await testTrace.removeAttribute('sponge'); + testTrace.removeAttribute('sponge'); expect( testTrace.getAttributes(), - completion({'patrick': 'star'}), - ); - - await testTrace.removeAttribute('sponge'); - expect( - testTrace.getAttributes(), - completion({'patrick': 'star'}), + {'patrick': 'star'}, ); }); - testWidgets('removeAttribute does nothing after the trace is stopped', - (WidgetTester tester) async { - await testTrace.start(); - await testTrace.putAttribute('sponge', 'bob'); - await testTrace.stop(); - await testTrace.removeAttribute('sponge'); - - expect( - testTrace.getAttributes(), - completion({'sponge': 'bob'}), - ); - }, skip: kIsWeb); - - testWidgets('getAttribute', (WidgetTester tester) async { - await testTrace.putAttribute('yugi', 'oh'); + test('getAttribute', () async { + testTrace.putAttribute('yugi', 'oh'); expect(testTrace.getAttribute('yugi'), equals('oh')); - - await testTrace.start(); - await testTrace.stop(); expect(testTrace.getAttribute('yugi'), equals('oh')); }); }); - group('$HttpMetric', () { - FirebasePerformance performance; - HttpMetric testHttpMetric; - - setUpAll(() async { - performance = FirebasePerformance.instance; - await performance.setPerformanceCollectionEnabled(true); - }); - - setUp(() { - testHttpMetric = performance.newHttpMetric( - 'https://www.google.com/', - HttpMethod.Delete, - ); - }); - - tearDown(() { - testHttpMetric.stop(); - testHttpMetric = null; - }); - - testWidgets( - 'putAttribute works correctly before the HTTP metric is stopped', - (WidgetTester tester) async { - await testHttpMetric.putAttribute('apple', 'sauce'); - await testHttpMetric.putAttribute('banana', 'pie'); - - expect( - testHttpMetric.getAttributes(), - completion({'apple': 'sauce', 'banana': 'pie'}), - ); - - await testHttpMetric.putAttribute('apple', 'sauce2'); - expect( - testHttpMetric.getAttributes(), - completion({'apple': 'sauce2', 'banana': 'pie'}), - ); - }); - - testWidgets('putAttribute does nothing after the HTTP metric is stopped', - (WidgetTester tester) async { - await testHttpMetric.start(); - await testHttpMetric.stop(); - await testHttpMetric.putAttribute('apple', 'sauce'); - - expect( - testHttpMetric.getAttributes(), - completion({}), - ); - }); + group( + '$HttpMetric', + () { + late FirebasePerformance performance; + late HttpMetric testHttpMetric; - testWidgets( - 'removeAttribute works correctly before the HTTP metric is stopped', - (WidgetTester tester) async { - await testHttpMetric.putAttribute('sponge', 'bob'); - await testHttpMetric.putAttribute('patrick', 'star'); - await testHttpMetric.removeAttribute('sponge'); + setUpAll(() async { + performance = FirebasePerformance.instance; + await performance.setPerformanceCollectionEnabled(true); + }); - expect( - testHttpMetric.getAttributes(), - completion({'patrick': 'star'}), - ); - - await testHttpMetric.removeAttribute('sponge'); - expect( - testHttpMetric.getAttributes(), - completion({'patrick': 'star'}), - ); - }); + setUp(() async { + testHttpMetric = performance.newHttpMetric( + 'https://www.google.com/', + HttpMethod.Delete, + ); + }); - testWidgets('removeAttribute does nothing after the HTTP metric is stopped', - (WidgetTester tester) async { - await testHttpMetric.start(); - await testHttpMetric.putAttribute('sponge', 'bob'); - await testHttpMetric.stop(); - await testHttpMetric.removeAttribute('sponge'); + tearDown(() { + testHttpMetric.stop(); + }); - expect( - testHttpMetric.getAttributes(), - completion({'sponge': 'bob'}), - ); - }); + test('test all Http method values', () async { + FirebasePerformance performance = FirebasePerformance.instance; - testWidgets('getAttribute', (WidgetTester tester) async { - await testHttpMetric.putAttribute('yugi', 'oh'); + await Future.forEach(HttpMethod.values, (HttpMethod method) async { + final HttpMetric testMetric = performance.newHttpMetric( + 'https://www.google.com/', + method, + ); + await testMetric.start(); + await testMetric.stop(); + }); + }); - expect(testHttpMetric.getAttribute('yugi'), equals('oh')); + test('putAttribute works correctly', () { + testHttpMetric.putAttribute('apple', 'sauce'); + testHttpMetric.putAttribute('banana', 'pie'); - await testHttpMetric.start(); - await testHttpMetric.stop(); - expect(testHttpMetric.getAttribute('yugi'), equals('oh')); - }); - - testWidgets('set HTTP response code correctly', - (WidgetTester tester) async { - await testHttpMetric.start(); - testHttpMetric.httpResponseCode = 443; - expect(testHttpMetric.httpResponseCode, equals(443)); - }); + expect( + testHttpMetric.getAttributes(), + {'apple': 'sauce', 'banana': 'pie'}, + ); + }); - testWidgets('set request payload size correctly', - (WidgetTester tester) async { - await testHttpMetric.start(); - testHttpMetric.requestPayloadSize = 56734; - expect(testHttpMetric.requestPayloadSize, equals(56734)); - }); + test('removeAttribute works correctly', () { + testHttpMetric.putAttribute('sponge', 'bob'); + testHttpMetric.putAttribute('patrick', 'star'); + testHttpMetric.removeAttribute('sponge'); - testWidgets('set response payload size correctly', - (WidgetTester tester) async { - await testHttpMetric.start(); - testHttpMetric.responsePayloadSize = 4949; - expect(testHttpMetric.responsePayloadSize, equals(4949)); - }); + expect( + testHttpMetric.getAttributes(), + {'patrick': 'star'}, + ); - testWidgets('set response content type correctly', - (WidgetTester tester) async { - await testHttpMetric.start(); - testHttpMetric.responseContentType = '1984'; - expect(testHttpMetric.responseContentType, equals('1984')); - }); - }, skip: kIsWeb); + testHttpMetric.removeAttribute('sponge'); + expect( + testHttpMetric.getAttributes(), + {'patrick': 'star'}, + ); + }); + + test('getAttribute works correctly', () { + testHttpMetric.putAttribute('yugi', 'oh'); + + expect(testHttpMetric.getAttribute('yugi'), equals('oh')); + }); + + test('set HTTP response code correctly', () { + testHttpMetric.httpResponseCode = 443; + expect(testHttpMetric.httpResponseCode, equals(443)); + }); + + test('set request payload size correctly', () { + testHttpMetric.requestPayloadSize = 56734; + expect(testHttpMetric.requestPayloadSize, equals(56734)); + }); + + test('set response payload size correctly', () { + testHttpMetric.responsePayloadSize = 4949; + expect(testHttpMetric.responsePayloadSize, equals(4949)); + }); + + test('set response content type correctly', () { + testHttpMetric.responseContentType = 'content'; + expect(testHttpMetric.responseContentType, equals('content')); + }); + }, + skip: kIsWeb, + ); } + +void main() => drive.main(testsMain); diff --git a/packages/firebase_performance/firebase_performance/example/test_driver/firebase_performance_e2e_test.dart b/packages/firebase_performance/firebase_performance/example/test_driver/firebase_performance_e2e_test.dart index a038cdd97fbf..bb4e596d9ec0 100644 --- a/packages/firebase_performance/firebase_performance/example/test_driver/firebase_performance_e2e_test.dart +++ b/packages/firebase_performance/firebase_performance/example/test_driver/firebase_performance_e2e_test.dart @@ -1,6 +1,7 @@ -// ignore_for_file: require_trailing_commas -// @dart=2.9 +// Copyright 2021, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. -import 'package:e2e/e2e_driver.dart' as e2e; +import 'package:drive/drive_driver.dart' as drive; -void main() => e2e.main(); +void main() => drive.main(); diff --git a/packages/firebase_performance/firebase_performance/ios/Classes/FLTFirebasePerformancePlugin+Internal.h b/packages/firebase_performance/firebase_performance/ios/Classes/FLTFirebasePerformancePlugin+Internal.h index 888628b8325e..4547fb6acd11 100644 --- a/packages/firebase_performance/firebase_performance/ios/Classes/FLTFirebasePerformancePlugin+Internal.h +++ b/packages/firebase_performance/firebase_performance/ios/Classes/FLTFirebasePerformancePlugin+Internal.h @@ -1,7 +1,8 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. +// Copyright 2021 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 #import "FLTFirebasePerformancePlugin.h" @protocol MethodCallHandler diff --git a/packages/firebase_performance/firebase_performance/ios/Classes/FLTFirebasePerformancePlugin.h b/packages/firebase_performance/firebase_performance/ios/Classes/FLTFirebasePerformancePlugin.h index f5258a1c561d..af5b58d471dd 100644 --- a/packages/firebase_performance/firebase_performance/ios/Classes/FLTFirebasePerformancePlugin.h +++ b/packages/firebase_performance/firebase_performance/ios/Classes/FLTFirebasePerformancePlugin.h @@ -1,9 +1,12 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. +// Copyright 2021 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 #import +#import +#import + +@interface FLTFirebasePerformancePlugin : FLTFirebasePlugin -@interface FLTFirebasePerformancePlugin : NSObject @end diff --git a/packages/firebase_performance/firebase_performance/ios/Classes/FLTFirebasePerformancePlugin.m b/packages/firebase_performance/firebase_performance/ios/Classes/FLTFirebasePerformancePlugin.m index ed0ef6f29129..969532d0659c 100644 --- a/packages/firebase_performance/firebase_performance/ios/Classes/FLTFirebasePerformancePlugin.m +++ b/packages/firebase_performance/firebase_performance/ios/Classes/FLTFirebasePerformancePlugin.m @@ -1,9 +1,11 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2021 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 "FLTFirebasePerformancePlugin+Internal.h" +NSString *const kFLTFirebasePerformanceChannelName = @"plugins.flutter.io/firebase_performance"; + @implementation FLTFirebasePerformancePlugin static NSMutableDictionary> *methodHandlers; @@ -54,4 +56,29 @@ + (void)addMethodHandler:(NSNumber *)handle methodHandler:(id + (void)removeMethodHandler:(NSNumber *)handle { [methodHandlers removeObjectForKey:handle]; } + +#pragma mark - FLTFirebasePlugin + +- (void)didReinitializeFirebaseCore:(void (^)(void))completion { + if (completion != nil) { + completion(); + } +} + +- (NSDictionary *_Nonnull)pluginConstantsForFIRApp:(FIRApp *)firebase_app { + return @{}; +} + +- (NSString *_Nonnull)firebaseLibraryName { + return LIBRARY_NAME; +} + +- (NSString *_Nonnull)firebaseLibraryVersion { + return LIBRARY_VERSION; +} + +- (NSString *_Nonnull)flutterChannelName { + return kFLTFirebasePerformanceChannelName; +} + @end diff --git a/packages/firebase_performance/firebase_performance/ios/Classes/FLTHttpMetric.m b/packages/firebase_performance/firebase_performance/ios/Classes/FLTHttpMetric.m index 0b8ef9e54a83..7d75e054727a 100644 --- a/packages/firebase_performance/firebase_performance/ios/Classes/FLTHttpMetric.m +++ b/packages/firebase_performance/firebase_performance/ios/Classes/FLTHttpMetric.m @@ -23,20 +23,6 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result [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 ([@"HttpMetric#putAttribute" isEqualToString:call.method]) { - [self putAttribute:call result:result]; - } else if ([@"HttpMetric#removeAttribute" isEqualToString:call.method]) { - [self removeAttribute:call result:result]; - } else if ([@"HttpMetric#getAttributes" isEqualToString:call.method]) { - [self getAttributes:result]; } else { result(FlutterMethodNotImplemented); } @@ -48,64 +34,36 @@ - (void)start:(FlutterResult)result { } - (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 { + NSDictionary *attributes = call.arguments[@"attributes"]; + NSNumber *httpResponseCode = call.arguments[@"httpResponseCode"]; 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"]; + NSNumber *responsePayloadSize = call.arguments[@"responsePayloadSize"]; - if (![responseContentType isEqual:[NSNull null]]) { + [attributes + enumerateKeysAndObjectsUsingBlock:^(NSString *attributeName, NSString *value, BOOL *stop) { + [_metric setValue:value forAttribute:attributeName]; + }]; + + if (httpResponseCode != nil) { + _metric.responseCode = [httpResponseCode integerValue]; + } + if (responseContentType != nil) { _metric.responseContentType = responseContentType; } - result(nil); -} - -- (void)responsePayloadSize:(FlutterMethodCall *)call result:(FlutterResult)result { - NSNumber *responsePayloadSize = call.arguments[@"responsePayloadSize"]; - - if (![responsePayloadSize isEqual:[NSNull null]]) { + if (requestPayloadSize != nil) { + _metric.requestPayloadSize = [requestPayloadSize longValue]; + } + if (responsePayloadSize != nil) { _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); -} + [_metric stop]; -- (void)removeAttribute:(FlutterMethodCall *)call result:(FlutterResult)result { - NSString *name = call.arguments[@"name"]; + NSNumber *handle = call.arguments[@"handle"]; + [FLTFirebasePerformancePlugin removeMethodHandler:handle]; - [_metric removeAttribute:name]; result(nil); } -- (void)getAttributes:(FlutterResult)result { - result([_metric attributes]); -} @end diff --git a/packages/firebase_performance/firebase_performance/ios/Classes/FLTTrace.m b/packages/firebase_performance/firebase_performance/ios/Classes/FLTTrace.m index 15d675c2fb2a..7e6addaf442f 100644 --- a/packages/firebase_performance/firebase_performance/ios/Classes/FLTTrace.m +++ b/packages/firebase_performance/firebase_performance/ios/Classes/FLTTrace.m @@ -23,18 +23,6 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result [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 ([@"Trace#putAttribute" isEqualToString:call.method]) { - [self putAttribute:call result:result]; - } else if ([@"Trace#removeAttribute" isEqualToString:call.method]) { - [self removeAttribute:call result:result]; - } else if ([@"Trace#getAttributes" isEqualToString:call.method]) { - [self getAttributes:result]; } else { result(FlutterMethodNotImplemented); } @@ -46,53 +34,23 @@ - (void)start:(FlutterResult)result { } - (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"]; + NSDictionary *metrics = call.arguments[@"metrics"]; + NSDictionary *attributes = call.arguments[@"attributes"]; - [_trace incrementMetric:name byInt:value.longValue]; - result(nil); -} - -- (void)getMetric:(FlutterMethodCall *)call result:(FlutterResult)result { - NSString *name = call.arguments[@"name"]; + [metrics enumerateKeysAndObjectsUsingBlock:^(NSString *metricName, NSNumber *value, BOOL *stop) { + [_trace setIntValue:[value longLongValue] forMetric:metricName]; + }]; - 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"]; + [attributes + enumerateKeysAndObjectsUsingBlock:^(NSString *attributeName, NSString *value, BOOL *stop) { + [_trace setValue:value forAttribute:attributeName]; + }]; - [_trace setValue:value forAttribute:name]; - result(nil); -} + [_trace stop]; -- (void)removeAttribute:(FlutterMethodCall *)call result:(FlutterResult)result { - NSString *name = call.arguments[@"name"]; + NSNumber *handle = call.arguments[@"handle"]; + [FLTFirebasePerformancePlugin removeMethodHandler:handle]; - [_trace removeAttribute:name]; result(nil); } - -- (void)getAttributes:(FlutterResult)result { - result([_trace attributes]); -} @end diff --git a/packages/firebase_performance/firebase_performance/lib/src/firebase_performance.dart b/packages/firebase_performance/firebase_performance/lib/src/firebase_performance.dart index bef297a401d0..a26fd2454a7c 100644 --- a/packages/firebase_performance/firebase_performance/lib/src/firebase_performance.dart +++ b/packages/firebase_performance/firebase_performance/lib/src/firebase_performance.dart @@ -1,5 +1,4 @@ -// ignore_for_file: require_trailing_commas -// Copyright 2019 The Chromium Authors. All rights reserved. +// Copyright 2021 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. @@ -9,32 +8,64 @@ part of firebase_performance; /// /// You can get an instance by calling [FirebasePerformance.instance]. class FirebasePerformance extends FirebasePluginPlatform { - FirebasePerformance._() - : super(Firebase.app().name, 'plugins.flutter.io/firebase_performance'); + FirebasePerformance._({required this.app}) + : super(app.name, 'plugins.flutter.io/firebase_performance'); - late final _delegate = FirebasePerformancePlatform.instance; + // Cached and lazily loaded instance of [FirebasePerformancePlatform] to avoid + // creating a [MethodChannelFirebasePerformance] when not needed or creating an + // instance with the default app before a user specifies an app. + FirebasePerformancePlatform? _delegatePackingProperty; - /// Singleton of [FirebasePerformance]. - static final FirebasePerformance instance = FirebasePerformance._(); + /// Returns an instance using the default [FirebaseApp]. + static FirebasePerformance get instance { + FirebaseApp defaultAppInstance = Firebase.app(); + return FirebasePerformance.instanceFor(app: defaultAppInstance); + } + + static Map _firebasePerformanceInstances = {}; - /// Determines whether performance monitoring is enabled or disabled. + /// The [FirebaseApp] for this current [FirebaseMessaging] instance. + FirebaseApp app; + + /// Returns the underlying delegate implementation. /// - /// True if performance monitoring is enabled and false if performance + /// If called and no [_delegatePackingProperty] exists, it will first be + /// created and assigned before returning the delegate. + FirebasePerformancePlatform get _delegate { + return _delegatePackingProperty ??= FirebasePerformancePlatform.instanceFor( + app: app, + ); + } + + /// Returns an instance using a specified [FirebaseApp]. + factory FirebasePerformance.instanceFor({required FirebaseApp app}) { + return _firebasePerformanceInstances.putIfAbsent(app.name, () { + return FirebasePerformance._(app: app); + }); + } + + /// Determines whether custom performance monitoring is enabled or disabled. + /// + /// True if custom performance monitoring is enabled and false if performance /// monitoring is disabled. This is for dynamic enable/disable state. This /// does not reflect whether instrumentation is enabled/disabled. Future isPerformanceCollectionEnabled() { + // TODO: update API to match web & iOS for 'dataCollectionEnabled' & 'instrumentationEnabled' return _delegate.isPerformanceCollectionEnabled(); } - /// Enables or disables performance monitoring. + /// Enables or disables custom performance monitoring setup. /// /// This setting is persisted and applied on future invocations of your - /// application. By default, performance monitoring is enabled. + /// application. By default, custom performance monitoring is enabled. Future setPerformanceCollectionEnabled(bool enabled) { return _delegate.setPerformanceCollectionEnabled(enabled); } - /// Creates a [Trace] object with given [name]. + /// Creates a [Trace] object with given [name]. Traces can be used to measure + /// the time taken for a sequence of steps. Traces also include “Counters”. + /// Counters are used to track information which is cumulative in nature + /// (e.g., Bytes downloaded). /// /// The [name] requires no leading or trailing whitespace, no leading /// underscore _ character, and max length of [Trace.maxTraceNameLength] @@ -43,19 +74,10 @@ class FirebasePerformance extends FirebasePluginPlatform { return Trace._(_delegate.newTrace(name)); } - /// Creates [HttpMetric] for collecting performance for one request/response. + /// Creates a HttpMetric object for collecting network performance data for one + /// request/response. Only works for native apps. A stub class is created for web + /// which does nothing HttpMetric newHttpMetric(String url, HttpMethod httpMethod) { return HttpMetric._(_delegate.newHttpMetric(url, httpMethod)); } - - /// Creates a [Trace] object with given [name] and starts the trace. - /// - /// The [name] requires no leading or trailing whitespace, no leading - /// underscore _ character, max length of [Trace.maxTraceNameLength] - /// characters. - static Future startTrace(String name) async { - final Trace trace = instance.newTrace(name); - await trace.start(); - return trace; - } } diff --git a/packages/firebase_performance/firebase_performance/lib/src/http_metric.dart b/packages/firebase_performance/firebase_performance/lib/src/http_metric.dart index 100bb8439e23..8b7ef8fa2ba9 100644 --- a/packages/firebase_performance/firebase_performance/lib/src/http_metric.dart +++ b/packages/firebase_performance/firebase_performance/lib/src/http_metric.dart @@ -1,5 +1,4 @@ -// ignore_for_file: require_trailing_commas -// Copyright 2019 The Chromium Authors. All rights reserved. +// Copyright 2021 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. @@ -20,10 +19,10 @@ part of firebase_performance; /// It is highly recommended that one always calls `start()` and `stop()` on /// each created [HttpMetric] to avoid leaking on the platform side. class HttpMetric { - HttpMetricPlatform _delegate; - HttpMetric._(this._delegate); + HttpMetricPlatform _delegate; + /// HttpResponse code of the request. int? get httpResponseCode => _delegate.httpResponseCode; @@ -106,7 +105,7 @@ class HttpMetric { /// /// If this object has been stopped, this method returns without adding the /// attribute. - Future putAttribute(String name, String value) { + void putAttribute(String name, String value) { return _delegate.putAttribute(name, value); } @@ -114,7 +113,7 @@ class HttpMetric { /// /// If this object has been stopped, this method returns without removing the /// attribute. - Future removeAttribute(String name) { + void removeAttribute(String name) { return _delegate.removeAttribute(name); } @@ -124,7 +123,7 @@ class HttpMetric { String? getAttribute(String name) => _delegate.getAttribute(name); /// All attributes added. - Future> getAttributes() async { + Map getAttributes() { return _delegate.getAttributes(); } } diff --git a/packages/firebase_performance/firebase_performance/lib/src/trace.dart b/packages/firebase_performance/firebase_performance/lib/src/trace.dart index d2854ebf3b76..4df710d4bb10 100644 --- a/packages/firebase_performance/firebase_performance/lib/src/trace.dart +++ b/packages/firebase_performance/firebase_performance/lib/src/trace.dart @@ -1,5 +1,4 @@ -// ignore_for_file: require_trailing_commas -// Copyright 2019 The Chromium Authors. All rights reserved. +// Copyright 2021 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. @@ -23,10 +22,10 @@ part of firebase_performance; /// It is highly recommended that one always calls `start()` and `stop()` on /// each created [Trace] to not avoid leaking on the platform side. class Trace { - final TracePlatform _delegate; - Trace._(this._delegate); + final TracePlatform _delegate; + /// Starts this [Trace]. /// /// Can only be called once. @@ -54,7 +53,7 @@ class Trace { /// If the metric does not exist, a new one will be created. If the [Trace] has /// not been started or has already been stopped, returns immediately without /// taking action. - Future incrementMetric(String name, int value) { + void incrementMetric(String name, int value) { return _delegate.incrementMetric(name, value); } @@ -63,7 +62,7 @@ class Trace { /// If a metric with the given name doesn't exist, a new one will be created. /// If the [Trace] has not been started or has already been stopped, returns /// immediately without taking action. - Future setMetric(String name, int value) { + void setMetric(String name, int value) { return _delegate.setMetric(name, value); } @@ -71,7 +70,7 @@ class Trace { /// /// If a metric with the given name doesn't exist, it is NOT created and a 0 /// is returned. - Future getMetric(String name) async { + int getMetric(String name) { return _delegate.getMetric(name); } @@ -91,7 +90,7 @@ class Trace { /// /// If this object has been stopped, this method returns without adding the /// attribute. - Future putAttribute(String name, String value) { + void putAttribute(String name, String value) { return _delegate.putAttribute(name, value); } @@ -99,7 +98,7 @@ class Trace { /// /// If this object has been stopped, this method returns without removing the /// attribute. - Future removeAttribute(String name) { + void removeAttribute(String name) { return _delegate.removeAttribute(name); } @@ -109,7 +108,7 @@ class Trace { String? getAttribute(String name) => _delegate.getAttribute(name); /// All attributes added. - Future> getAttributes() async { + Map getAttributes() { return _delegate.getAttributes(); } } diff --git a/packages/firebase_performance/firebase_performance/pubspec.yaml b/packages/firebase_performance/firebase_performance/pubspec.yaml index f6dca9e34e48..80912eb4cfae 100644 --- a/packages/firebase_performance/firebase_performance/pubspec.yaml +++ b/packages/firebase_performance/firebase_performance/pubspec.yaml @@ -35,8 +35,8 @@ flutter: plugin: platforms: android: - package: io.flutter.plugins.firebaseperformance - pluginClass: FirebasePerformancePlugin + package: io.flutter.plugins.firebase.performance + pluginClass: FlutterFirebasePerformancePlugin ios: pluginClass: FLTFirebasePerformancePlugin web: diff --git a/packages/firebase_performance/firebase_performance/test/firebase_performance_test.dart b/packages/firebase_performance/firebase_performance/test/firebase_performance_test.dart index 21a014567250..7a0016ebb129 100644 --- a/packages/firebase_performance/firebase_performance/test/firebase_performance_test.dart +++ b/packages/firebase_performance/firebase_performance/test/firebase_performance_test.dart @@ -1,5 +1,4 @@ -// ignore_for_file: require_trailing_commas -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2021 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. @@ -13,10 +12,9 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import './mock.dart'; MockFirebasePerformance mockPerformancePlatform = MockFirebasePerformance(); -MockTracePlatform mockTracePlatform = MockTracePlatform('foo'); +MockTracePlatform mockTracePlatform = MockTracePlatform(); String mockUrl = 'https://example.com'; -MockHttpMetricPlatform mockHttpMetricPlatform = - MockHttpMetricPlatform(mockUrl, HttpMethod.Get); +MockHttpMetricPlatform mockHttpMetricPlatform = MockHttpMetricPlatform(); void main() { setupFirebasePerformanceMocks(); @@ -24,24 +22,35 @@ void main() { late FirebasePerformance performance; group('$FirebasePerformance', () { - FirebasePerformancePlatform.instance = mockPerformancePlatform; + when(mockPerformancePlatform.delegateFor(app: anyNamed('app'))) + .thenReturn(mockPerformancePlatform); setUpAll(() async { await Firebase.initializeApp(); + FirebasePerformancePlatform.instance = mockPerformancePlatform; performance = FirebasePerformance.instance; }); - group('performanceCollectionEnabled', () { - when(mockPerformancePlatform.isPerformanceCollectionEnabled()) - .thenAnswer((_) => Future.value(true)); - when(mockPerformancePlatform.setPerformanceCollectionEnabled(true)) - .thenAnswer((_) => Future.value()); + group('instance', () { + test('test instance is singleton', () async { + FirebasePerformance performance1 = FirebasePerformance.instance; + FirebasePerformance performance2 = FirebasePerformance.instance; + expect(performance1, isA()); + expect(identical(performance1, performance2), isTrue); + }); + }); + + group('performanceCollectionEnabled', () { test('getter should call delegate method', () async { + when(mockPerformancePlatform.isPerformanceCollectionEnabled()) + .thenAnswer((_) => Future.value(true)); await performance.isPerformanceCollectionEnabled(); verify(mockPerformancePlatform.isPerformanceCollectionEnabled()); }); test('setter should call delegate method', () async { + when(mockPerformancePlatform.setPerformanceCollectionEnabled(true)) + .thenAnswer((_) => Future.value()); await performance.setPerformanceCollectionEnabled(true); verify(mockPerformancePlatform.setPerformanceCollectionEnabled(true)); }); @@ -50,8 +59,6 @@ void main() { group('trace', () { when(mockPerformancePlatform.newTrace('foo')) .thenReturn(mockTracePlatform); - when(FirebasePerformancePlatform.startTrace('foo')).thenAnswer( - (realInvocation) => Future.value(MockTracePlatform('foo'))); when(mockTracePlatform.start()) .thenAnswer((realInvocation) => Future.value()); when(mockTracePlatform.incrementMetric('bar', 8)) @@ -62,12 +69,6 @@ void main() { verify(mockPerformancePlatform.newTrace('foo')); }); - test('startTrace should call delegate methods', () async { - final trace = await FirebasePerformancePlatform.startTrace('foo'); - verify(mockPerformancePlatform.newTrace('foo')); - verify(trace.start()); - }); - test('start and stop should call delegate methods', () async { final trace = performance.newTrace('foo'); await trace.start(); @@ -78,19 +79,19 @@ void main() { test('incrementMetric should call delegate method', () async { final trace = performance.newTrace('foo'); - await trace.incrementMetric('bar', 8); + trace.incrementMetric('bar', 8); verify(mockTracePlatform.incrementMetric('bar', 8)); }); test('setMetric should call delegate method', () async { final trace = performance.newTrace('foo'); - await trace.setMetric('bar', 8); + trace.setMetric('bar', 8); verify(mockTracePlatform.setMetric('bar', 8)); }); test('getMetric should call delegate method', () async { final trace = performance.newTrace('foo'); - await trace.getMetric('bar'); + trace.getMetric('bar'); verify(mockTracePlatform.getMetric('bar')); }); }); @@ -128,26 +129,33 @@ void main() { verify(mockHttpMetricPlatform.responsePayloadSize); }); - test('httpResponseCode setter should call delegate setter', () async { + test('set httpResponseCode setter should call delegate setter', () async { final httpMetric = performance.newHttpMetric(mockUrl, HttpMethod.Get); - httpMetric.responsePayloadSize = 8080; - verify(mockHttpMetricPlatform.responsePayloadSize = 8080); + when(mockHttpMetricPlatform.httpResponseCode = 8080).thenReturn(0); + httpMetric.httpResponseCode = 8080; + verify(mockHttpMetricPlatform.httpResponseCode = 8080); }); - test('requestPayloadSize setter should call delegate setter', () async { + test('set requestPayloadSize setter should call delegate setter', + () async { final httpMetric = performance.newHttpMetric(mockUrl, HttpMethod.Get); + when(mockHttpMetricPlatform.requestPayloadSize = 8).thenReturn(0); httpMetric.requestPayloadSize = 8; verify(mockHttpMetricPlatform.requestPayloadSize = 8); }); - test('responsePayloadSize setter should call delegate setter', () async { + test('setResponsePayloadSize setter should call delegate setter', + () async { final httpMetric = performance.newHttpMetric(mockUrl, HttpMethod.Get); - httpMetric.responsePayloadSize = 8; - verify(mockHttpMetricPlatform.responsePayloadSize = 8); + when(mockHttpMetricPlatform.responsePayloadSize = 99).thenReturn(0); + httpMetric.responsePayloadSize = 99; + verify(mockHttpMetricPlatform.responsePayloadSize = 99); }); - test('responseContentType setter should call delegate setter', () async { + test('set responseContentType setter should call delegate setter', + () async { final httpMetric = performance.newHttpMetric(mockUrl, HttpMethod.Get); + when(mockHttpMetricPlatform.responseContentType = 'foo').thenReturn(''); httpMetric.responseContentType = 'foo'; verify(mockHttpMetricPlatform.responseContentType = 'foo'); }); @@ -174,6 +182,15 @@ class MockFirebasePerformance extends Mock TestFirebasePerformancePlatform(); } + @override + FirebasePerformancePlatform delegateFor({FirebaseApp? app}) { + return super.noSuchMethod( + Invocation.method(#delegateFor, [], {#app: app}), + returnValue: TestFirebasePerformancePlatform(), + returnValueForMissingStub: TestFirebasePerformancePlatform(), + ); + } + @override Future isPerformanceCollectionEnabled() { return super.noSuchMethod( @@ -187,8 +204,8 @@ class MockFirebasePerformance extends Mock HttpMetricPlatform newHttpMetric(String url, HttpMethod httpMethod) { return super.noSuchMethod( Invocation.method(#newHttpMetric, [url, httpMethod]), - returnValue: MockHttpMetricPlatform(url, httpMethod), - returnValueForMissingStub: MockHttpMetricPlatform(url, httpMethod), + returnValue: MockHttpMetricPlatform(), + returnValueForMissingStub: MockHttpMetricPlatform(), ); } @@ -196,8 +213,8 @@ class MockFirebasePerformance extends Mock TracePlatform newTrace(String name) { return super.noSuchMethod( Invocation.method(#newTrace, [name]), - returnValue: MockTracePlatform(name), - returnValueForMissingStub: MockTracePlatform(name), + returnValue: MockTracePlatform(), + returnValueForMissingStub: MockTracePlatform(), ); } @@ -216,14 +233,14 @@ class TestFirebasePerformancePlatform extends FirebasePerformancePlatform { } class TestTracePlatform extends TracePlatform { - TestTracePlatform(String name) : super(name); + TestTracePlatform() : super(); } class MockTracePlatform extends Mock with MockPlatformInterfaceMixin implements TestTracePlatform { - MockTracePlatform(String name) { - TestTracePlatform(name); + MockTracePlatform() { + TestTracePlatform(); } @override @@ -263,25 +280,60 @@ class MockTracePlatform extends Mock } @override - Future getMetric(String name) { + int getMetric(String name) { return super.noSuchMethod( Invocation.method(#getMetric, [name]), - returnValue: Future.value(8), - returnValueForMissingStub: Future.value(8), + returnValue: 8, + returnValueForMissingStub: 8, ); } } class TestHttpMetricPlatform extends HttpMetricPlatform { - TestHttpMetricPlatform(String url, HttpMethod httpMethod) - : super(url, httpMethod); + TestHttpMetricPlatform() : super(); } class MockHttpMetricPlatform extends Mock with MockPlatformInterfaceMixin implements TestHttpMetricPlatform { - MockHttpMetricPlatform(String url, HttpMethod httpMethod) { - TestHttpMetricPlatform(url, httpMethod); + MockHttpMetricPlatform() { + TestHttpMetricPlatform(); + } + + @override + // ignore: avoid_setters_without_getters + set httpResponseCode(int? httpResponseCode) { + // ignore: void_checks + return super.noSuchMethod( + Invocation.setter(#httpResponseCode, [httpResponseCode]), + ); + } + + @override + // ignore: avoid_setters_without_getters + set requestPayloadSize(int? requestPayloadSize) { + // ignore: void_checks + return super.noSuchMethod( + Invocation.setter(#requestPayloadSize, [requestPayloadSize]), + ); + } + + @override + // ignore: avoid_setters_without_getters + set responsePayloadSize(int? responsePayloadSize) { + // ignore: void_checks + return super.noSuchMethod( + Invocation.setter(#responsePayloadSize, [responsePayloadSize]), + ); + } + + @override + // ignore: avoid_setters_without_getters + set responseContentType(String? responseContentType) { + // ignore: void_checks + return super.noSuchMethod( + Invocation.setter(#responseContentType, [responseContentType]), + ); } @override diff --git a/packages/firebase_performance/firebase_performance/test/mock.dart b/packages/firebase_performance/firebase_performance/test/mock.dart index 1d2740f31a4a..f593daed0c01 100644 --- a/packages/firebase_performance/firebase_performance/test/mock.dart +++ b/packages/firebase_performance/firebase_performance/test/mock.dart @@ -1,3 +1,7 @@ +// Copyright 2021 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 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/firebase_performance/firebase_performance_platform_interface/lib/firebase_performance_platform_interface.dart b/packages/firebase_performance/firebase_performance_platform_interface/lib/firebase_performance_platform_interface.dart index 7b2c00a1a8eb..08ca6a8550dc 100644 --- a/packages/firebase_performance/firebase_performance_platform_interface/lib/firebase_performance_platform_interface.dart +++ b/packages/firebase_performance/firebase_performance_platform_interface/lib/firebase_performance_platform_interface.dart @@ -1,3 +1,7 @@ +// Copyright 2021 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. + library firebase_performance_platform_interface; export 'src/platform_interface/platform_interface_firebase_performance.dart'; diff --git a/packages/firebase_performance/firebase_performance_platform_interface/lib/src/method_channel/method_channel_firebase_performance.dart b/packages/firebase_performance/firebase_performance_platform_interface/lib/src/method_channel/method_channel_firebase_performance.dart index 8f3fa5d0d8f5..f28474f84b73 100644 --- a/packages/firebase_performance/firebase_performance_platform_interface/lib/src/method_channel/method_channel_firebase_performance.dart +++ b/packages/firebase_performance/firebase_performance_platform_interface/lib/src/method_channel/method_channel_firebase_performance.dart @@ -1,74 +1,88 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. +// Copyright 2021 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 'package:firebase_core/firebase_core.dart'; import 'package:firebase_performance_platform_interface/src/method_channel/method_channel_trace.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import '../../firebase_performance_platform_interface.dart'; import '../platform_interface/platform_interface_firebase_performance.dart'; import 'method_channel_http_metric.dart'; +import 'utils/exception.dart'; -/// The method channel implementation of [FirebaseAnalyticsPlatform]. +/// The method channel implementation of [FirebasePerformancePlatform]. class MethodChannelFirebasePerformance extends FirebasePerformancePlatform { + MethodChannelFirebasePerformance({required FirebaseApp app}) + : _handle = _nextHandle++, + super(appInstance: app); static const MethodChannel channel = MethodChannel('plugins.flutter.io/firebase_performance'); + /// Internal stub class initializer. + /// + /// When the user code calls an auth method, the real instance is + /// then initialized via the [delegateFor] method. MethodChannelFirebasePerformance._() - : _handle = _nextHandle++, + : _handle = 0, super(); static int _nextHandle = 0; final int _handle; + @visibleForTesting + static void clearState() { + //TODO refactor 'handle' system. MethodChannel doesn't needs its own handle. + _nextHandle = 0; + } + /// Returns a stub instance to allow the platform interface to access /// the class instance statically. static MethodChannelFirebasePerformance get instance { return MethodChannelFirebasePerformance._(); } + /// Instances are cached and reused for incoming event handlers. + @override + FirebasePerformancePlatform delegateFor({required FirebaseApp app}) { + return MethodChannelFirebasePerformance(app: app); + } + @override Future isPerformanceCollectionEnabled() async { - final isPerformanceCollectionEnabled = await channel.invokeMethod( - 'FirebasePerformance#isPerformanceCollectionEnabled', - {'handle': _handle}, - ); - return isPerformanceCollectionEnabled!; + try { + final isPerformanceCollectionEnabled = await channel.invokeMethod( + 'FirebasePerformance#isPerformanceCollectionEnabled', + {'handle': _handle}, + ); + return isPerformanceCollectionEnabled!; + } catch (e, s) { + throw convertPlatformException(e, s); + } } @override - Future setPerformanceCollectionEnabled(bool enabled) { - return channel.invokeMethod( - 'FirebasePerformance#setPerformanceCollectionEnabled', - {'handle': _handle, 'enable': enabled}, - ); + Future setPerformanceCollectionEnabled(bool enabled) async { + try { + await channel.invokeMethod( + 'FirebasePerformance#setPerformanceCollectionEnabled', + {'handle': _handle, 'enable': enabled}, + ); + } catch (e, s) { + throw convertPlatformException(e, s); + } } @override TracePlatform newTrace(String name) { - final int handle = _nextHandle++; - - channel.invokeMethod( - 'FirebasePerformance#newTrace', - {'handle': _handle, 'traceHandle': handle, 'name': name}, - ); - - return MethodChannelTrace(handle, name); + final int traceHandle = _nextHandle++; + return MethodChannelTrace(_handle, traceHandle, name); } @override HttpMetricPlatform newHttpMetric(String url, HttpMethod httpMethod) { - final int handle = _nextHandle++; - - channel.invokeMethod( - 'FirebasePerformance#newHttpMetric', - { - 'handle': _handle, - 'httpMetricHandle': handle, - 'url': url, - 'httpMethod': httpMethod.toString(), - }, - ); - - return MethodChannelHttpMetric(handle, url, httpMethod); + final int httpMetricHandle = _nextHandle++; + return MethodChannelHttpMetric(_handle, httpMetricHandle, url, httpMethod); } } diff --git a/packages/firebase_performance/firebase_performance_platform_interface/lib/src/method_channel/method_channel_http_metric.dart b/packages/firebase_performance/firebase_performance_platform_interface/lib/src/method_channel/method_channel_http_metric.dart index f7a550c17506..268416d11d4c 100644 --- a/packages/firebase_performance/firebase_performance_platform_interface/lib/src/method_channel/method_channel_http_metric.dart +++ b/packages/firebase_performance/firebase_performance_platform_interface/lib/src/method_channel/method_channel_http_metric.dart @@ -1,17 +1,29 @@ +// Copyright 2021 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 '../../firebase_performance_platform_interface.dart'; import 'method_channel_firebase_performance.dart'; +import 'utils/exception.dart'; class MethodChannelHttpMetric extends HttpMetricPlatform { - MethodChannelHttpMetric(this._handle, String url, HttpMethod httpMethod) - : super(url, httpMethod); - - final int _handle; - + MethodChannelHttpMetric( + this._methodChannelHandle, + this._httpMetricHandle, + this._url, + this._httpMethod, + ) : super(); + + final int _methodChannelHandle; + final int _httpMetricHandle; + final String _url; + final HttpMethod _httpMethod; int? _httpResponseCode; int? _requestPayloadSize; String? _responseContentType; int? _responsePayloadSize; + bool _hasStarted = false; bool _hasStopped = false; final Map _attributes = {}; @@ -30,128 +42,89 @@ class MethodChannelHttpMetric extends HttpMetricPlatform { @override set httpResponseCode(int? httpResponseCode) { - if (_hasStopped) return; - _httpResponseCode = httpResponseCode; - MethodChannelFirebasePerformance.channel.invokeMethod( - 'HttpMetric#httpResponseCode', - { - 'handle': _handle, - 'httpResponseCode': httpResponseCode, - }, - ); } @override set requestPayloadSize(int? requestPayloadSize) { - if (_hasStopped) return; - _requestPayloadSize = requestPayloadSize; - MethodChannelFirebasePerformance.channel.invokeMethod( - 'HttpMetric#requestPayloadSize', - { - 'handle': _handle, - 'requestPayloadSize': requestPayloadSize, - }, - ); } @override set responseContentType(String? responseContentType) { - if (_hasStopped) return; - _responseContentType = responseContentType; - MethodChannelFirebasePerformance.channel.invokeMethod( - 'HttpMetric#responseContentType', - { - 'handle': _handle, - 'responseContentType': responseContentType, - }, - ); } @override set responsePayloadSize(int? responsePayloadSize) { - if (_hasStopped) return; - _responsePayloadSize = responsePayloadSize; - MethodChannelFirebasePerformance.channel.invokeMethod( - 'HttpMetric#responsePayloadSize', - { - 'handle': _handle, - 'responsePayloadSize': responsePayloadSize, - }, - ); } @override - Future start() { - if (_hasStopped) return Future.value(); - - return MethodChannelFirebasePerformance.channel.invokeMethod( - 'HttpMetric#start', - {'handle': _handle}, - ); + Future start() async { + if (_hasStopped) return; + try { + //TODO: update so that the method call & handle is passed on one method channel call (start()) instead. + await MethodChannelFirebasePerformance.channel.invokeMethod( + 'FirebasePerformance#newHttpMetric', + { + 'handle': _methodChannelHandle, + 'httpMetricHandle': _httpMetricHandle, + 'url': _url, + 'httpMethod': _httpMethod.toString(), + }, + ); + await MethodChannelFirebasePerformance.channel.invokeMethod( + 'HttpMetric#start', + {'handle': _httpMetricHandle}, + ); + _hasStarted = true; + } catch (e, s) { + throw convertPlatformException(e, s); + } } @override - Future stop() { - if (_hasStopped) return Future.value(); - - _hasStopped = true; - return MethodChannelFirebasePerformance.channel.invokeMethod( - 'HttpMetric#stop', - {'handle': _handle}, - ); + Future stop() async { + if (!_hasStarted || _hasStopped) return; + try { + await MethodChannelFirebasePerformance.channel.invokeMethod( + 'HttpMetric#stop', + { + 'handle': _httpMetricHandle, + 'attributes': _attributes, + 'httpResponseCode': _httpResponseCode, + 'requestPayloadSize': _requestPayloadSize, + 'responseContentType': _responseContentType, + 'responsePayloadSize': _responsePayloadSize, + }, + ); + _hasStopped = true; + } catch (e, s) { + throw convertPlatformException(e, s); + } } @override - Future putAttribute(String name, String value) { - if (_hasStopped || - name.length > HttpMetricPlatform.maxAttributeKeyLength || + void putAttribute(String name, String value) { + if (name.length > HttpMetricPlatform.maxAttributeKeyLength || value.length > HttpMetricPlatform.maxAttributeValueLength || _attributes.length == HttpMetricPlatform.maxCustomAttributes) { - return Future.value(); + return; } - _attributes[name] = value; - return MethodChannelFirebasePerformance.channel.invokeMethod( - 'HttpMetric#putAttribute', - { - 'handle': _handle, - 'name': name, - 'value': value, - }, - ); } @override - Future removeAttribute(String name) { - if (_hasStopped) return Future.value(); - + void removeAttribute(String name) { _attributes.remove(name); - return MethodChannelFirebasePerformance.channel.invokeMethod( - 'HttpMetric#removeAttribute', - {'handle': _handle, 'name': name}, - ); } @override String? getAttribute(String name) => _attributes[name]; @override - Future> getAttributes() async { - if (_hasStopped) { - return Future>.value( - Map.unmodifiable(_attributes), - ); - } - - final attributes = await MethodChannelFirebasePerformance.channel - .invokeMapMethod( - 'HttpMetric#getAttributes', - {'handle': _handle}, - ); - return attributes ?? {}; + Map getAttributes() { + return {..._attributes}; } } diff --git a/packages/firebase_performance/firebase_performance_platform_interface/lib/src/method_channel/method_channel_trace.dart b/packages/firebase_performance/firebase_performance_platform_interface/lib/src/method_channel/method_channel_trace.dart index 3a5e9c79435d..6c3ce4f8f0c5 100644 --- a/packages/firebase_performance/firebase_performance_platform_interface/lib/src/method_channel/method_channel_trace.dart +++ b/packages/firebase_performance/firebase_performance_platform_interface/lib/src/method_channel/method_channel_trace.dart @@ -1,10 +1,18 @@ +// Copyright 2021 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 '../../firebase_performance_platform_interface.dart'; import 'method_channel_firebase_performance.dart'; +import 'utils/exception.dart'; class MethodChannelTrace extends TracePlatform { - MethodChannelTrace(this._handle, String name) : super(name); + MethodChannelTrace(this._methodChannelHandle, this._traceHandle, this._name) + : super(); - final int _handle; + final int _methodChannelHandle; + final int _traceHandle; + final String _name; bool _hasStarted = false; bool _hasStopped = false; @@ -15,110 +23,83 @@ class MethodChannelTrace extends TracePlatform { static const int maxTraceNameLength = 100; @override - Future start() { - if (_hasStopped) return Future.value(); - - _hasStarted = true; - return MethodChannelFirebasePerformance.channel.invokeMethod( - 'Trace#start', - {'handle': _handle}, - ); + Future start() async { + if (_hasStopped) return; + + try { + //TODO: update so that the method call & handle is passed on one method channel call (start()) instead. + await MethodChannelFirebasePerformance.channel.invokeMethod( + 'FirebasePerformance#newTrace', + { + 'handle': _methodChannelHandle, + 'traceHandle': _traceHandle, + 'name': _name + }, + ); + await MethodChannelFirebasePerformance.channel.invokeMethod( + 'Trace#start', + {'handle': _traceHandle}, + ); + _hasStarted = true; + } catch (e, s) { + throw convertPlatformException(e, s); + } } @override - Future stop() { - if (_hasStopped) return Future.value(); - - _hasStopped = true; - return MethodChannelFirebasePerformance.channel.invokeMethod( - 'Trace#stop', - {'handle': _handle}, - ); + Future stop() async { + if (!_hasStarted || _hasStopped) return; + + try { + await MethodChannelFirebasePerformance.channel.invokeMethod( + 'Trace#stop', + { + 'handle': _traceHandle, + 'metrics': _metrics, + 'attributes': _attributes + }, + ); + _hasStopped = true; + } catch (e, s) { + throw convertPlatformException(e, s); + } } @override - Future incrementMetric(String name, int value) { - if (!_hasStarted || _hasStopped) { - return Future.value(); - } - + void incrementMetric(String name, int value) { _metrics[name] = (_metrics[name] ?? 0) + value; - return MethodChannelFirebasePerformance.channel.invokeMethod( - 'Trace#incrementMetric', - {'handle': _handle, 'name': name, 'value': value}, - ); } @override - Future setMetric(String name, int value) { - if (!_hasStarted || _hasStopped) return Future.value(); - + void setMetric(String name, int value) { _metrics[name] = value; - return MethodChannelFirebasePerformance.channel.invokeMethod( - 'Trace#setMetric', - {'handle': _handle, 'name': name, 'value': value}, - ); } @override - Future getMetric(String name) async { - if (_hasStopped) return Future.value(_metrics[name] ?? 0); - - final metric = - await MethodChannelFirebasePerformance.channel.invokeMethod( - 'Trace#getMetric', - {'handle': _handle, 'name': name}, - ); - return metric ?? 0; + int getMetric(String name) { + return _metrics[name] ?? 0; } @override - Future putAttribute(String name, String value) { - if (_hasStopped || - name.length > TracePlatform.maxAttributeKeyLength || + void putAttribute(String name, String value) { + if (name.length > TracePlatform.maxAttributeKeyLength || value.length > TracePlatform.maxAttributeValueLength || _attributes.length == TracePlatform.maxCustomAttributes) { - return Future.value(); + return; } - _attributes[name] = value; - return MethodChannelFirebasePerformance.channel.invokeMethod( - 'Trace#putAttribute', - { - 'handle': _handle, - 'name': name, - 'value': value, - }, - ); } @override - Future removeAttribute(String name) { - if (_hasStopped) return Future.value(); - + void removeAttribute(String name) { _attributes.remove(name); - return MethodChannelFirebasePerformance.channel.invokeMethod( - 'Trace#removeAttribute', - {'handle': _handle, 'name': name}, - ); } @override String? getAttribute(String name) => _attributes[name]; @override - Future> getAttributes() async { - if (_hasStopped) { - return Future>.value( - Map.unmodifiable(_attributes), - ); - } - - final attributes = await MethodChannelFirebasePerformance.channel - .invokeMapMethod( - 'Trace#getAttributes', - {'handle': _handle}, - ); - return attributes ?? {}; + Map getAttributes() { + return {..._attributes}; } } diff --git a/packages/firebase_performance/firebase_performance_platform_interface/lib/src/method_channel/utils/exception.dart b/packages/firebase_performance/firebase_performance_platform_interface/lib/src/method_channel/utils/exception.dart new file mode 100644 index 000000000000..6e9577f2fda2 --- /dev/null +++ b/packages/firebase_performance/firebase_performance_platform_interface/lib/src/method_channel/utils/exception.dart @@ -0,0 +1,46 @@ +// Copyright 2021, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/services.dart'; + +/// Catches a [PlatformException] and converts it into a [FirebaseException] if +/// it was intentionally caught on the native platform. +Exception convertPlatformException(Object exception, [StackTrace? stackTrace]) { + if (exception is! Exception || exception is! PlatformException) { + // ignore: only_throw_errors + throw exception; + } + + return platformExceptionToFirebaseException(exception, stackTrace); +} + +/// Converts a [PlatformException] into a [FirebaseException]. +/// +/// A [PlatformException] can only be converted to a [FirebaseException] if the +/// `details` of the exception exist. Firebase returns specific codes and messages +/// which can be converted into user friendly exceptions. +FirebaseException platformExceptionToFirebaseException( + PlatformException platformException, [ + StackTrace? stackTrace, +]) { + Map? details = platformException.details != null + ? Map.from(platformException.details) + : null; + + String code = 'unknown'; + String message = platformException.message ?? ''; + + if (details != null) { + code = details['code'] ?? code; + message = details['message'] ?? message; + } + + return FirebaseException( + plugin: 'firebase_performance', + code: code, + message: message, + stackTrace: stackTrace, + ); +} diff --git a/packages/firebase_performance/firebase_performance_platform_interface/lib/src/platform_interface/platform_interface_firebase_performance.dart b/packages/firebase_performance/firebase_performance_platform_interface/lib/src/platform_interface/platform_interface_firebase_performance.dart index ee46fc7a57fa..1420c550f991 100644 --- a/packages/firebase_performance/firebase_performance_platform_interface/lib/src/platform_interface/platform_interface_firebase_performance.dart +++ b/packages/firebase_performance/firebase_performance_platform_interface/lib/src/platform_interface/platform_interface_firebase_performance.dart @@ -1,13 +1,16 @@ +// Copyright 2021 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 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_performance_platform_interface/firebase_performance_platform_interface.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import '../method_channel/method_channel_firebase_performance.dart'; -import 'platform_interface_http_metric.dart'; -import 'platform_interface_trace.dart'; enum HttpMethod { Connect, Delete, Get, Head, Options, Patch, Post, Put, Trace } @@ -25,6 +28,7 @@ abstract class FirebasePerformancePlatform extends PlatformInterface { FirebasePerformancePlatform({this.appInstance}) : super(token: _token); static FirebasePerformancePlatform? _instance; + static final Object _token = Object(); /// The current default [FirebasePerformancePlatform] instance. @@ -41,6 +45,13 @@ abstract class FirebasePerformancePlatform extends PlatformInterface { _instance = instance; } + /// Create an instance with a [FirebaseApp] using an existing instance. + factory FirebasePerformancePlatform.instanceFor({ + required FirebaseApp app, + }) { + return FirebasePerformancePlatform.instance.delegateFor(app: app); + } + /// The [FirebaseApp] this instance was initialized with. @protected final FirebaseApp? appInstance; @@ -48,33 +59,46 @@ abstract class FirebasePerformancePlatform extends PlatformInterface { /// Returns the [FirebaseApp] for the current instance. FirebaseApp get app => appInstance ?? Firebase.app(); - /// Only works for native apps. Always returns true for web apps. - Future isPerformanceCollectionEnabled() async { + /// Enables delegates to create new instances of themselves if a none default + /// [FirebaseApp] instance is required by the user. Currently only default Firebase app only. + @protected + FirebasePerformancePlatform delegateFor({required FirebaseApp app}) { + throw UnimplementedError('delegateFor() is not implemented'); + } + + /// Determines whether custom performance monitoring is enabled or disabled. + /// + /// True if custom performance monitoring is enabled and false if performance + /// monitoring is disabled. This is for dynamic enable/disable state. This + /// does not reflect whether instrumentation is enabled/disabled. + Future isPerformanceCollectionEnabled() { throw UnimplementedError( 'isPerformanceCollectionEnabled() is not implemented', ); } - /// Only works for native apps. Does nothing for web apps. - Future setPerformanceCollectionEnabled(bool enabled) async { + /// Enables or disables custom performance monitoring setup. + /// + /// This setting is persisted and applied on future invocations of your + /// application. By default, custom performance monitoring is enabled. + Future setPerformanceCollectionEnabled(bool enabled) { throw UnimplementedError( 'setPerformanceCollectionEnabled() is not implemented', ); } + /// Creates a Trace object with given name. Traces can be used to measure + /// the time taken for a sequence of steps. Traces also include “Counters”. + /// Counters are used to track information which is cumulative in nature + /// (e.g., Bytes downloaded). TracePlatform newTrace(String name) { throw UnimplementedError('newTrace() is not implemented'); } - /// Only works for native apps. Does nothing for web apps. + /// Creates a HttpMetric object for collecting network performance data for one + /// request/response. Only works for native apps. A stub class is created for web + /// which does nothing. HttpMetricPlatform newHttpMetric(String url, HttpMethod httpMethod) { throw UnimplementedError('newHttpMetric() is not implemented'); } - - /// Creates a Trace object with given name and start the trace. - static Future startTrace(String name) async { - final trace = FirebasePerformancePlatform.instance.newTrace(name); - await trace.start(); - return trace; - } } diff --git a/packages/firebase_performance/firebase_performance_platform_interface/lib/src/platform_interface/platform_interface_http_metric.dart b/packages/firebase_performance/firebase_performance_platform_interface/lib/src/platform_interface/platform_interface_http_metric.dart index 950533e4d2c7..8e10616649ac 100644 --- a/packages/firebase_performance/firebase_performance_platform_interface/lib/src/platform_interface/platform_interface_http_metric.dart +++ b/packages/firebase_performance/firebase_performance_platform_interface/lib/src/platform_interface/platform_interface_http_metric.dart @@ -1,12 +1,17 @@ -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +// Copyright 2021 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 'platform_interface_firebase_performance.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; abstract class HttpMetricPlatform extends PlatformInterface { - HttpMetricPlatform(this.url, this.httpMethod) : super(token: Object()); + HttpMetricPlatform() : super(token: _token); + static final Object _token = Object(); + + /// Ensures that any delegate class has extended a [HttpMetricPlatform]. static void verifyExtends(HttpMetricPlatform instance) { - PlatformInterface.verifyToken(instance, Object); + PlatformInterface.verifyToken(instance, _token); } /// Maximum allowed length of a key passed to [putAttribute]. @@ -18,66 +23,73 @@ abstract class HttpMetricPlatform extends PlatformInterface { /// Maximum allowed number of attributes that can be added. static const int maxCustomAttributes = 5; - final String url; - final HttpMethod httpMethod; - /// HttpResponse code of the request. int? get httpResponseCode { - throw UnimplementedError('getHttpResponseCode() is not implemented'); + throw UnimplementedError('get httpResponseCode is not implemented'); } /// Size of the request payload. int? get requestPayloadSize { - throw UnimplementedError('getRequestPayloadSize() is not implemented'); + throw UnimplementedError('get requestPayloadSize is not implemented'); } /// Content type of the response such as text/html, application/json, etc... String? get responseContentType { - throw UnimplementedError('getResponseContentType() is not implemented'); + throw UnimplementedError('get responseContentType is not implemented'); } /// Size of the response payload. int? get responsePayloadSize { - throw UnimplementedError('getResponsePayloadSize() is not implemented'); + throw UnimplementedError('get responsePayloadSize is not implemented'); } + /// Sets the httpResponse code of the request set httpResponseCode(int? httpResponseCode) { - throw UnimplementedError('setHttpResponseCode() is not implemented'); + throw UnimplementedError('set httpResponseCode() is not implemented'); } + /// Sets the size of the request payload set requestPayloadSize(int? requestPayloadSize) { - throw UnimplementedError('setRequestPayloadSize() is not implemented'); + throw UnimplementedError('set requestPayloadSize() is not implemented'); } + /// Sets the size of the response payload set responsePayloadSize(int? responsePayloadSize) { - throw UnimplementedError('setResponsePayload() is not implemented'); + throw UnimplementedError('set responsePayload() is not implemented'); } + /// Content type of the response such as text/html, application/json, etc.. set responseContentType(String? responseContentType) { - throw UnimplementedError('setResponseContentType() is not implemented'); + throw UnimplementedError('set responseContentType() is not implemented'); } + /// Marks the start time of the request Future start() { throw UnimplementedError('start() is not implemented'); } + /// Marks the end time of the response and queues the network request metric on the device for transmission. Future stop() { throw UnimplementedError('stop() is not implemented'); } - Future putAttribute(String name, String value) { + /// Sets a value as a string for the specified attribute. Updates the value of the attribute if a value had already existed. + void putAttribute(String name, String value) { throw UnimplementedError('putAttribute() is not implemented'); } - Future removeAttribute(String name) { + /// Removes an attribute from the list. Does nothing if the attribute does not exist. + void removeAttribute(String name) { throw UnimplementedError('removeAttribute() is not implemented'); } + /// Returns the value of an attribute. String? getAttribute(String name) { throw UnimplementedError('getAttribute() is not implemented'); } - Future> getAttributes() { + /// Returns the map of all the attributes added to this HttpMetric. + Map getAttributes() { throw UnimplementedError('getAttributes() is not implemented'); } } diff --git a/packages/firebase_performance/firebase_performance_platform_interface/lib/src/platform_interface/platform_interface_trace.dart b/packages/firebase_performance/firebase_performance_platform_interface/lib/src/platform_interface/platform_interface_trace.dart index 351bfdc7810b..37d7ed722436 100644 --- a/packages/firebase_performance/firebase_performance_platform_interface/lib/src/platform_interface/platform_interface_trace.dart +++ b/packages/firebase_performance/firebase_performance_platform_interface/lib/src/platform_interface/platform_interface_trace.dart @@ -1,14 +1,20 @@ +// Copyright 2021 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 'package:plugin_platform_interface/plugin_platform_interface.dart'; abstract class TracePlatform extends PlatformInterface { - TracePlatform(this.name) : super(token: Object()); + TracePlatform() : super(token: _token); static void verifyExtends(TracePlatform instance) { - PlatformInterface.verifyToken(instance, Object()); + PlatformInterface.verifyToken(instance, _token); } + static final Object _token = Object(); + /// Maximum allowed length of a key passed to [putAttribute]. static const int maxAttributeKeyLength = 40; @@ -18,42 +24,48 @@ abstract class TracePlatform extends PlatformInterface { /// Maximum allowed number of attributes that can be added. static const int maxCustomAttributes = 5; - final String name; - + /// Starts this trace. Future start() { throw UnimplementedError('start() is not implemented'); } + /// Stops this trace. Future stop() { throw UnimplementedError('stop() is not implemented'); } - Future incrementMetric(String name, int value) { + /// increments the metric with the given name in this trace by the value. + void incrementMetric(String name, int value) { throw UnimplementedError('incrementMetric() is not implemented'); } - /// Only works for native apps. Does nothing for web apps. - Future setMetric(String name, int value) { + /// Sets the value of the metric with the given name in this trace to the value provided + void setMetric(String name, int value) { throw UnimplementedError('setMetric() is not implemented'); } - Future putAttribute(String name, String value) { + /// Gets the value of the metric with the given name in the current trace. + int getMetric(String name) { + throw UnimplementedError('getMetric() is not implemented'); + } + + /// Sets a String value for the specified attribute. + void putAttribute(String name, String value) { throw UnimplementedError('putAttribute() is not implemented'); } - Future removeAttribute(String name) { + /// Removes an already added attribute from the Traces. + void removeAttribute(String name) { throw UnimplementedError('removeAttribute() is not implemented'); } + /// Returns the value of an attribute. String? getAttribute(String name) { throw UnimplementedError('getAttribute() is not implemented'); } - Future> getAttributes() { + /// Returns the map of all the attributes added to this trace. + Map getAttributes() { throw UnimplementedError('getAttributes() is not implemented'); } - - Future getMetric(String name) { - throw UnimplementedError('getMetric() is not implemented'); - } } diff --git a/packages/firebase_performance/firebase_performance_platform_interface/pubspec.yaml b/packages/firebase_performance/firebase_performance_platform_interface/pubspec.yaml index 650c42e07a50..376f6b8a9c14 100644 --- a/packages/firebase_performance/firebase_performance_platform_interface/pubspec.yaml +++ b/packages/firebase_performance/firebase_performance_platform_interface/pubspec.yaml @@ -14,5 +14,6 @@ dependencies: plugin_platform_interface: ^2.0.1 dev_dependencies: + firebase_core_platform_interface: ^4.1.0 flutter_test: sdk: flutter diff --git a/packages/firebase_performance/firebase_performance_platform_interface/test/method_channel_tests/method_channel_firebase_performance_test.dart b/packages/firebase_performance/firebase_performance_platform_interface/test/method_channel_tests/method_channel_firebase_performance_test.dart new file mode 100644 index 000000000000..cd5112ecb238 --- /dev/null +++ b/packages/firebase_performance/firebase_performance_platform_interface/test/method_channel_tests/method_channel_firebase_performance_test.dart @@ -0,0 +1,152 @@ +// Copyright 2021, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_performance_platform_interface/firebase_performance_platform_interface.dart'; +import 'package:firebase_performance_platform_interface/src/method_channel/method_channel_firebase_performance.dart'; +import 'package:firebase_performance_platform_interface/src/method_channel/method_channel_trace.dart'; +import 'package:firebase_performance_platform_interface/src/method_channel/method_channel_http_metric.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../mock.dart'; + +void main() { + setupFirebasePerformanceMocks(); + + late FirebasePerformancePlatform performance; + late FirebaseApp app; + final List log = []; + + // mock props + bool mockPlatformExceptionThrown = false; + bool mockExceptionThrown = false; + + group('$MethodChannelFirebasePerformance', () { + setUpAll(() async { + app = await Firebase.initializeApp(); + + handleMethodCall((call) async { + log.add(call); + + if (mockExceptionThrown) { + throw Exception(); + } else if (mockPlatformExceptionThrown) { + throw PlatformException(code: 'UNKNOWN'); + } + + switch (call.method) { + case 'FirebasePerformance#isPerformanceCollectionEnabled': + return true; + case 'FirebasePerformance#setPerformanceCollectionEnabled': + return call.arguments['enable']; + case 'FirebasePerformance#newTrace': + return null; + case 'FirebasePerformance#newHttpMetric': + return null; + default: + return true; + } + }); + + performance = MethodChannelFirebasePerformance(app: app); + }); + }); + + setUp(() async { + mockPlatformExceptionThrown = false; + mockExceptionThrown = false; + log.clear(); + }); + + tearDown(() async { + mockPlatformExceptionThrown = false; + mockExceptionThrown = false; + MethodChannelFirebasePerformance.clearState(); + }); + + test('instance', () { + final testPerf = MethodChannelFirebasePerformance.instance; + + expect(testPerf, isA()); + }); + + test('delegateFor', () { + final testPerf = TestMethodChannelFirebasePerformance(Firebase.app()); + final result = testPerf.delegateFor(app: Firebase.app()); + + expect(result, isA()); + expect(result.app, isA()); + }); + + group('isPerformanceCollectionEnabled', () { + test('should call delegate method successfully', () async { + await performance.isPerformanceCollectionEnabled(); + + expect(log, [ + isMethodCall( + 'FirebasePerformance#isPerformanceCollectionEnabled', + arguments: {'handle': 0}, + ) + ]); + }); + + test( + 'catch a [PlatformException] error and throws a [FirebaseException] error', + () async { + mockPlatformExceptionThrown = true; + + await testExceptionHandling( + 'PLATFORM', + performance.isPerformanceCollectionEnabled, + ); + }); + }); + + group('setPerformanceCollectionEnabled', () { + test('should call delegate method successfully', () async { + await performance.setPerformanceCollectionEnabled(true); + + expect(log, [ + isMethodCall( + 'FirebasePerformance#setPerformanceCollectionEnabled', + arguments: {'handle': 0, 'enable': true}, + ) + ]); + }); + + test( + 'catch a [PlatformException] error and throws a [FirebaseException] error', + () async { + mockPlatformExceptionThrown = true; + + await testExceptionHandling( + 'PLATFORM', + () => performance.setPerformanceCollectionEnabled(true), + ); + }); + }); + + group('newTrace', () { + test('should call delegate method successfully', () { + final trace = performance.newTrace('trace-name'); + + expect(trace, isA()); + }); + }); + + group('newHttpMetric', () { + test('should call delegate method successfully', () { + final httpMetric = + performance.newHttpMetric('http-metric-url', HttpMethod.Get); + + expect(httpMetric, isA()); + }); + }); +} + +class TestMethodChannelFirebasePerformance + extends MethodChannelFirebasePerformance { + TestMethodChannelFirebasePerformance(FirebaseApp app) : super(app: app); +} diff --git a/packages/firebase_performance/firebase_performance_platform_interface/test/method_channel_tests/method_channel_http_metric_test.dart b/packages/firebase_performance/firebase_performance_platform_interface/test/method_channel_tests/method_channel_http_metric_test.dart new file mode 100644 index 000000000000..ba054402bc33 --- /dev/null +++ b/packages/firebase_performance/firebase_performance_platform_interface/test/method_channel_tests/method_channel_http_metric_test.dart @@ -0,0 +1,307 @@ +// Copyright 2021, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_performance_platform_interface/firebase_performance_platform_interface.dart'; +import 'package:firebase_performance_platform_interface/src/method_channel/method_channel_http_metric.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../mock.dart'; + +void main() { + setupFirebasePerformanceMocks(); + + late TestMethodChannelHttpMetric httpMetric; + const int kMethodChannelHandle = 23; + const int kHttpMetricHandle = 24; + const String kUrl = 'https://test-url.com'; + const HttpMethod kMethod = HttpMethod.Get; + final List log = []; + // mock props + bool mockPlatformExceptionThrown = false; + bool mockExceptionThrown = false; + + group('$FirebasePerformancePlatform()', () { + setUpAll(() async { + await Firebase.initializeApp(); + + handleMethodCall((call) async { + log.add(call); + + if (mockExceptionThrown) { + throw Exception(); + } else if (mockPlatformExceptionThrown) { + throw PlatformException(code: 'UNKNOWN'); + } + + switch (call.method) { + case 'FirebasePerformance#newHttpMetric': + case 'HttpMetric#start': + case 'HttpMetric#stop': + return null; + default: + return true; + } + }); + }); + + setUp(() async { + httpMetric = TestMethodChannelHttpMetric( + kMethodChannelHandle, + kHttpMetricHandle, + kUrl, + kMethod, + ); + mockPlatformExceptionThrown = false; + mockExceptionThrown = false; + log.clear(); + }); + + tearDown(() async { + mockPlatformExceptionThrown = false; + mockExceptionThrown = false; + }); + + test('instance', () { + expect(httpMetric, isA()); + expect(httpMetric, isA()); + }); + + group('start', () { + test('should call delegate method successfully', () async { + await httpMetric.start(); + + expect(log, [ + isMethodCall( + 'FirebasePerformance#newHttpMetric', + arguments: { + 'handle': kMethodChannelHandle, + 'httpMetricHandle': kHttpMetricHandle, + 'url': kUrl, + 'httpMethod': kMethod.toString(), + }, + ), + isMethodCall( + 'HttpMetric#start', + arguments: { + 'handle': kHttpMetricHandle, + }, + ) + ]); + }); + + test( + 'catch a [PlatformException] error and throws a [FirebaseException] error', + () async { + mockPlatformExceptionThrown = true; + + await testExceptionHandling('PLATFORM', httpMetric.start); + }); + }); + + group('stop', () { + test('should call delegate method successfully', () async { + await httpMetric.start(); + + httpMetric.putAttribute('foo', 'bar'); + httpMetric.httpResponseCode = 2; + httpMetric.requestPayloadSize = 28; + httpMetric.responseContentType = 'baz'; + httpMetric.responsePayloadSize = 23; + await httpMetric.stop(); + + expect(log, [ + isMethodCall( + 'FirebasePerformance#newHttpMetric', + arguments: { + 'handle': kMethodChannelHandle, + 'httpMetricHandle': kHttpMetricHandle, + 'url': kUrl, + 'httpMethod': kMethod.toString() + }, + ), + isMethodCall( + 'HttpMetric#start', + arguments: { + 'handle': kHttpMetricHandle, + }, + ), + isMethodCall( + 'HttpMetric#stop', + arguments: { + 'handle': kHttpMetricHandle, + 'attributes': { + 'foo': 'bar', + }, + 'httpResponseCode': 2, + 'requestPayloadSize': 28, + 'responseContentType': 'baz', + 'responsePayloadSize': 23, + }, + ) + ]); + }); + + test("will immediately return if start() hasn't been called first", + () async { + await httpMetric.stop(); + expect(log, []); + }); + + test( + 'catch a [PlatformException] error and throws a [FirebaseException] error', + () async { + await httpMetric.start(); + mockPlatformExceptionThrown = true; + + await testExceptionHandling('PLATFORM', httpMetric.stop); + }); + }); + + group('httpResponseCode', () { + test('httpResponseCode', () async { + httpMetric.httpResponseCode = 3; + expect(httpMetric.httpResponseCode, 3); + }); + }); + + group('requestPayloadSize', () { + test('requestPayloadSize', () async { + httpMetric.requestPayloadSize = 23; + expect(httpMetric.requestPayloadSize, 23); + }); + }); + + group('responseContentType', () { + test('responseContentType', () async { + httpMetric.responseContentType = 'content'; + expect(httpMetric.responseContentType, 'content'); + }); + }); + + group('responsePayloadSize', () { + test('responsePayloadSize', () async { + httpMetric.responsePayloadSize = 45; + expect(httpMetric.responsePayloadSize, 45); + }); + }); + + group('putAttribute', () { + test('should call delegate method successfully', () async { + const String attributeName = 'test-attribute-name'; + const String attributeValue = 'foo'; + httpMetric.putAttribute(attributeName, attributeValue); + expect(log, []); + expect(httpMetric.getAttribute(attributeName), attributeValue); + }); + + test( + "will immediately return if name length is longer than 'HttpMetricPlatform.maxAttributeKeyLength' ", + () async { + String longName = + 'thisisaverylongnamethatislongerthanthe40charactersallowedbyHttpMetricPlatformmaxAttributeKeyLengthwaywaylongertogetover100charlimit'; + const String attributeValue = 'foo'; + httpMetric.putAttribute(longName, attributeValue); + expect(log, []); + expect(httpMetric.getAttribute(longName), isNull); + }); + + test( + "will immediately return if value length is longer than 'HttpMetricPlatform.maxAttributeValueLength' ", + () async { + String attributeName = 'foo'; + String longValue = + 'thisisaverylongnamethatislongerthanthe40charactersallowedbyHttpMetricPlatformmaxAttributeKeyLengthwaywaylongertogetover100charlimit'; + httpMetric.putAttribute(attributeName, longValue); + expect(log, []); + expect(httpMetric.getAttribute(attributeName), isNull); + }); + + test( + "will immediately return if attribute map has more properties than 'HttpMetricPlatform.maxCustomAttributes' allows", + () async { + String attributeName1 = 'foo'; + String attributeName2 = 'bar'; + String attributeName3 = 'baz'; + String attributeName4 = 'too'; + String attributeName5 = 'yoo'; + String attributeName6 = 'who'; + String attributeValue = 'bar'; + httpMetric.putAttribute(attributeName1, attributeValue); + httpMetric.putAttribute(attributeName2, attributeValue); + httpMetric.putAttribute(attributeName3, attributeValue); + httpMetric.putAttribute(attributeName4, attributeValue); + httpMetric.putAttribute(attributeName5, attributeValue); + httpMetric.putAttribute(attributeName6, attributeValue); + + expect(log, []); + + expect(httpMetric.getAttribute(attributeName5), attributeValue); + expect(httpMetric.getAttribute(attributeName6), isNull); + }); + }); + + group('removeAttribute', () { + test('should call delegate method successfully', () async { + const String attributeName = 'test-attribute-name'; + const String attributeValue = 'barr'; + httpMetric.putAttribute(attributeName, attributeValue); + httpMetric.removeAttribute(attributeName); + expect(log, []); + expect(httpMetric.getAttribute(attributeName), isNull); + }); + }); + + group('getAttribute', () { + test('should call delegate method successfully', () async { + const String attributeName = 'test-attribute-name'; + const String attributeValue = 'mario'; + httpMetric.putAttribute(attributeName, attributeValue); + httpMetric.getAttribute(attributeName); + expect(log, []); + }); + }); + + group('getAttributes', () { + test('should call delegate method successfully', () async { + String attributeName1 = 'foo'; + String attributeName2 = 'bar'; + String attributeName3 = 'baz'; + String attributeName4 = 'too'; + String attributeName5 = 'yoo'; + String attributeValue = 'bar'; + httpMetric.putAttribute(attributeName1, attributeValue); + httpMetric.putAttribute(attributeName2, attributeValue); + httpMetric.putAttribute(attributeName3, attributeValue); + httpMetric.putAttribute(attributeName4, attributeValue); + httpMetric.putAttribute(attributeName5, attributeValue); + + Map attributes = { + attributeName1: attributeValue, + attributeName2: attributeValue, + attributeName3: attributeValue, + attributeName4: attributeValue, + attributeName5: attributeValue, + }; + + expect(log, []); + expect(httpMetric.getAttributes(), attributes); + }); + }); + }); +} + +class TestFirebasePerformancePlatform extends FirebasePerformancePlatform { + TestFirebasePerformancePlatform(FirebaseApp app) : super(appInstance: app); +} + +class TestMethodChannelHttpMetric extends MethodChannelHttpMetric { + TestMethodChannelHttpMetric( + methodChannelHandle, + httpMetricHandle, + url, + method, + ) : super(methodChannelHandle, httpMetricHandle, url, method); +} diff --git a/packages/firebase_performance/firebase_performance_platform_interface/test/method_channel_tests/method_channel_trace_test.dart b/packages/firebase_performance/firebase_performance_platform_interface/test/method_channel_tests/method_channel_trace_test.dart new file mode 100644 index 000000000000..90420ca6c7a6 --- /dev/null +++ b/packages/firebase_performance/firebase_performance_platform_interface/test/method_channel_tests/method_channel_trace_test.dart @@ -0,0 +1,342 @@ +// Copyright 2021, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_performance_platform_interface/firebase_performance_platform_interface.dart'; +import 'package:firebase_performance_platform_interface/src/method_channel/method_channel_trace.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../mock.dart'; + +void main() { + setupFirebasePerformanceMocks(); + + late TestMethodChannelTrace trace; + const int kMethodHandle = 21; + const int kTraceHandle = 22; + const String kName = 'test-trace-name'; + final List log = []; + // mock props + bool mockPlatformExceptionThrown = false; + bool mockExceptionThrown = false; + + group('$FirebasePerformancePlatform()', () { + setUpAll(() async { + await Firebase.initializeApp(); + + handleMethodCall((call) async { + log.add(call); + + if (mockExceptionThrown) { + throw Exception(); + } else if (mockPlatformExceptionThrown) { + throw PlatformException(code: 'UNKNOWN'); + } + + switch (call.method) { + case 'FirebasePerformance#newTrace': + case 'Trace#start': + case 'Trace#stop': + return null; + default: + return true; + } + }); + }); + setUp(() async { + trace = TestMethodChannelTrace(kMethodHandle, kTraceHandle, kName); + mockPlatformExceptionThrown = false; + mockExceptionThrown = false; + log.clear(); + }); + + tearDown(() async { + mockPlatformExceptionThrown = false; + mockExceptionThrown = false; + }); + + test('instance', () { + expect(trace, isA()); + expect(trace, isA()); + }); + + group('start', () { + test('should call delegate method successfully', () async { + await trace.start(); + + expect(log, [ + isMethodCall( + 'FirebasePerformance#newTrace', + arguments: { + 'handle': kMethodHandle, + 'traceHandle': kTraceHandle, + 'name': kName + }, + ), + isMethodCall( + 'Trace#start', + arguments: { + 'handle': kTraceHandle, + }, + ) + ]); + }); + + test( + 'catch a [PlatformException] error and throws a [FirebaseException] error', + () async { + mockPlatformExceptionThrown = true; + + await testExceptionHandling('PLATFORM', trace.start); + }); + }); + + group('stop', () { + test('should call delegate method successfully', () async { + await trace.start(); + trace.putAttribute('bar', 'baz'); + trace.setMetric('yoo', 33); + + await trace.stop(); + + expect(log, [ + isMethodCall( + 'FirebasePerformance#newTrace', + arguments: { + 'handle': kMethodHandle, + 'traceHandle': kTraceHandle, + 'name': kName + }, + ), + isMethodCall( + 'Trace#start', + arguments: { + 'handle': kTraceHandle, + }, + ), + isMethodCall( + 'Trace#stop', + arguments: { + 'handle': kTraceHandle, + 'metrics': { + 'yoo': 33, + }, + 'attributes': { + 'bar': 'baz', + }, + }, + ) + ]); + }); + + test("will immediately return if start() hasn't been called first", + () async { + await trace.stop(); + expect(log, []); + }); + + test( + 'catch a [PlatformException] error and throws a [FirebaseException] error', + () async { + await trace.start(); + mockPlatformExceptionThrown = true; + + await testExceptionHandling('PLATFORM', trace.stop); + }); + }); + + group('incrementMetric', () { + const String metricName = 'test-metric-name'; + const int metricValue = 453; + test('should call delegate method successfully', () async { + await trace.start(); + trace.incrementMetric(metricName, metricValue); + + expect(log, [ + isMethodCall( + 'FirebasePerformance#newTrace', + arguments: { + 'handle': kMethodHandle, + 'traceHandle': kTraceHandle, + 'name': kName + }, + ), + isMethodCall( + 'Trace#start', + arguments: { + 'handle': kTraceHandle, + }, + ), + ]); + + expect(trace.getMetric(metricName), metricValue); + }); + }); + + group('setMetric', () { + const String metricName = 'test-metric-name'; + const int metricValue = 4; + test('should call delegate method successfully', () async { + await trace.start(); + trace.setMetric(metricName, metricValue); + + expect(log, [ + isMethodCall( + 'FirebasePerformance#newTrace', + arguments: { + 'handle': kMethodHandle, + 'traceHandle': kTraceHandle, + 'name': kName + }, + ), + isMethodCall( + 'Trace#start', + arguments: { + 'handle': kTraceHandle, + }, + ), + ]); + + expect(trace.getMetric(metricName), metricValue); + }); + }); + + group('getMetric', () { + const String metricName = 'test-metric-name'; + const int metricValue = 546; + test('should call delegate method successfully', () async { + await trace.start(); + trace.setMetric(metricName, metricValue); + + expect(log, [ + isMethodCall( + 'FirebasePerformance#newTrace', + arguments: { + 'handle': kMethodHandle, + 'traceHandle': kTraceHandle, + 'name': kName + }, + ), + isMethodCall( + 'Trace#start', + arguments: { + 'handle': kTraceHandle, + }, + ), + ]); + + expect(trace.getMetric(metricName), metricValue); + }); + }); + + group('putAttribute', () { + test('should call delegate method successfully', () async { + const String attributeName = 'test-attribute-name'; + const String attributeValue = 'foo'; + trace.putAttribute(attributeName, attributeValue); + expect(log, []); + expect(trace.getAttribute(attributeName), attributeValue); + }); + + test( + "will immediately return if name length is longer than 'TracePlatform.maxAttributeKeyLength' ", + () async { + String longName = + 'thisisaverylongnamethatislongerthanthe40charactersallowedbyTracePlatformmaxAttributeKeyLengthwaywaylongertogetover100charlimit'; + const String attributeValue = 'foo'; + trace.putAttribute(longName, attributeValue); + expect(log, []); + expect(trace.getAttribute(longName), isNull); + }); + + test( + "will immediately return if value length is longer than 'TracePlatform.maxAttributeValueLength' ", + () async { + String attributeName = 'foo'; + String longValue = + 'thisisaverylongnamethatislongerthanthe40charactersallowedbyTracePlatformmaxAttributeKeyLengthwaywaylongertogetover100charlimit'; + trace.putAttribute(attributeName, longValue); + expect(log, []); + expect(trace.getAttribute(attributeName), isNull); + }); + + test( + "will immediately return if attribute map has more properties than 'TracePlatform.maxCustomAttributes' allows", + () async { + String attributeName1 = 'foo'; + String attributeName2 = 'bar'; + String attributeName3 = 'baz'; + String attributeName4 = 'too'; + String attributeName5 = 'yoo'; + String attributeName6 = 'who'; + String attributeValue = 'bar'; + trace.putAttribute(attributeName1, attributeValue); + trace.putAttribute(attributeName2, attributeValue); + trace.putAttribute(attributeName3, attributeValue); + trace.putAttribute(attributeName4, attributeValue); + trace.putAttribute(attributeName5, attributeValue); + trace.putAttribute(attributeName6, attributeValue); + + expect(log, []); + + expect(trace.getAttribute(attributeName5), attributeValue); + expect(trace.getAttribute(attributeName6), isNull); + }); + }); + + group('removeAttribute', () { + test('should call delegate method successfully', () async { + const String attributeName = 'test-attribute-name'; + const String attributeValue = 'barr'; + trace.putAttribute(attributeName, attributeValue); + trace.removeAttribute(attributeName); + expect(log, []); + expect(trace.getAttribute(attributeName), isNull); + }); + }); + + group('getAttribute', () { + test('should call delegate method successfully', () async { + const String attributeName = 'test-attribute-name'; + const String attributeValue = 'mario'; + trace.putAttribute(attributeName, attributeValue); + trace.getAttribute(attributeName); + expect(log, []); + }); + }); + + group('getAttributes', () { + test('should call delegate method successfully', () async { + String attributeName1 = 'foo'; + String attributeName2 = 'bar'; + String attributeName3 = 'baz'; + String attributeName4 = 'too'; + String attributeName5 = 'yoo'; + String attributeValue = 'bar'; + trace.putAttribute(attributeName1, attributeValue); + trace.putAttribute(attributeName2, attributeValue); + trace.putAttribute(attributeName3, attributeValue); + trace.putAttribute(attributeName4, attributeValue); + trace.putAttribute(attributeName5, attributeValue); + + Map attributes = { + attributeName1: attributeValue, + attributeName2: attributeValue, + attributeName3: attributeValue, + attributeName4: attributeValue, + attributeName5: attributeValue, + }; + + expect(log, []); + expect(trace.getAttributes(), attributes); + }); + }); + }); +} + +class TestMethodChannelTrace extends MethodChannelTrace { + TestMethodChannelTrace(handle, traceHandle, name) + : super(handle, traceHandle, name); +} diff --git a/packages/firebase_performance/firebase_performance_platform_interface/test/mock.dart b/packages/firebase_performance/firebase_performance_platform_interface/test/mock.dart new file mode 100644 index 000000000000..21a0511b435d --- /dev/null +++ b/packages/firebase_performance/firebase_performance_platform_interface/test/mock.dart @@ -0,0 +1,71 @@ +// Copyright 2021 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 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:firebase_performance_platform_interface/src/method_channel/method_channel_firebase_performance.dart'; + +typedef MethodCallCallback = dynamic Function(MethodCall methodCall); +typedef Callback = void Function(MethodCall call); + +int mockHandleId = 0; +int get nextMockHandleId => mockHandleId++; + +void setupFirebasePerformanceMocks([Callback? customHandlers]) { + TestWidgetsFlutterBinding.ensureInitialized(); + + MethodChannelFirebase.channel.setMockMethodCallHandler((call) async { + if (call.method == 'Firebase#initializeCore') { + return [ + { + 'name': defaultFirebaseAppName, + 'options': { + 'apiKey': '123', + 'appId': '123', + 'messagingSenderId': '123', + 'projectId': '123', + }, + 'pluginConstants': {}, + } + ]; + } + + if (call.method == 'Firebase#initializeApp') { + return { + 'name': call.arguments['appName'], + 'options': call.arguments['options'], + 'pluginConstants': {}, + }; + } + + if (customHandlers != null) { + customHandlers(call); + } + + return null; + }); +} + +void handleMethodCall(MethodCallCallback methodCallCallback) => + MethodChannelFirebasePerformance.channel + .setMockMethodCallHandler((call) async { + return await methodCallCallback(call); + }); + +Future testExceptionHandling( + String type, + void Function() testMethod, +) async { + await expectLater( + () async => testMethod(), + anyOf([ + completes, + if (type == 'PLATFORM' || type == 'EXCEPTION') + throwsA(isA()) + ]), + ); +} diff --git a/packages/firebase_performance/firebase_performance_platform_interface/test/platform_interface_tests/platform_interface_firebase_performance_test.dart b/packages/firebase_performance/firebase_performance_platform_interface/test/platform_interface_tests/platform_interface_firebase_performance_test.dart new file mode 100644 index 000000000000..1b94cb377bf9 --- /dev/null +++ b/packages/firebase_performance/firebase_performance_platform_interface/test/platform_interface_tests/platform_interface_firebase_performance_test.dart @@ -0,0 +1,114 @@ +// Copyright 2021, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_performance_platform_interface/firebase_performance_platform_interface.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import '../mock.dart'; + +void main() { + setupFirebasePerformanceMocks(); + + late TestFirebasePerformancePlatform firebasePerformancePlatform; + + late FirebaseApp app; + group('$FirebasePerformancePlatform()', () { + setUpAll(() async { + app = await Firebase.initializeApp(); + + firebasePerformancePlatform = TestFirebasePerformancePlatform( + app, + ); + }); + + test('Constructor', () { + expect(firebasePerformancePlatform, isA()); + expect(firebasePerformancePlatform, isA()); + }); + + test('FirebasePerformancePlatform.instanceFor', () { + final result = FirebasePerformancePlatform.instanceFor( + app: app, + ); + expect(result, isA()); + }); + + test('get.instance', () { + expect( + FirebasePerformancePlatform.instance, + isA(), + ); + expect( + FirebasePerformancePlatform.instance.app.name, + equals(defaultFirebaseAppName), + ); + }); + + group('set.instance', () { + test('sets the current instance', () { + FirebasePerformancePlatform.instance = + TestFirebasePerformancePlatform(app); + + expect( + FirebasePerformancePlatform.instance, + isA(), + ); + expect( + FirebasePerformancePlatform.instance.app.name, + equals('[DEFAULT]'), + ); + }); + }); + + test('throws if .delegateFor', () { + expect( + // ignore: invalid_use_of_protected_member + () => firebasePerformancePlatform.delegateFor(app: app), + throwsUnimplementedError, + ); + }); + + test('throws if .delegateFor', () { + expect( + // ignore: invalid_use_of_protected_member + () => firebasePerformancePlatform.delegateFor(app: app), + throwsUnimplementedError, + ); + }); + + test('throws if .isPerformanceCollectionEnabled', () { + expect( + firebasePerformancePlatform.isPerformanceCollectionEnabled, + throwsUnimplementedError, + ); + }); + + test('throws if .setPerformanceCollectionEnabled', () { + expect( + () => firebasePerformancePlatform.setPerformanceCollectionEnabled(true), + throwsUnimplementedError, + ); + }); + + test('throws if .newTrace', () { + expect( + () => firebasePerformancePlatform.newTrace('name'), + throwsUnimplementedError, + ); + }); + + test('throws if .newHttpMetric', () { + expect( + () => firebasePerformancePlatform.newHttpMetric('url', HttpMethod.Get), + throwsUnimplementedError, + ); + }); + }); +} + +class TestFirebasePerformancePlatform extends FirebasePerformancePlatform { + TestFirebasePerformancePlatform(FirebaseApp app) : super(appInstance: app); +} diff --git a/packages/firebase_performance/firebase_performance_platform_interface/test/platform_interface_tests/platform_interface_http_metric_test.dart b/packages/firebase_performance/firebase_performance_platform_interface/test/platform_interface_tests/platform_interface_http_metric_test.dart new file mode 100644 index 000000000000..81eb8a31ffda --- /dev/null +++ b/packages/firebase_performance/firebase_performance_platform_interface/test/platform_interface_tests/platform_interface_http_metric_test.dart @@ -0,0 +1,130 @@ +// Copyright 2021, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_performance_platform_interface/firebase_performance_platform_interface.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import '../mock.dart'; + +void main() { + setupFirebasePerformanceMocks(); + + late TestHttpMetricPlatform httpMetricPlatform; + + group('$HttpMetricPlatform()', () { + setUpAll(() async { + await Firebase.initializeApp(); + + httpMetricPlatform = TestHttpMetricPlatform(); + }); + }); + test('Constructor', () { + expect(httpMetricPlatform, isA()); + expect(httpMetricPlatform, isA()); + }); + + test('static maxAttributeKeyLength', () { + expect(HttpMetricPlatform.maxAttributeKeyLength, 40); + }); + + test('static maxCustomAttributes', () { + expect(HttpMetricPlatform.maxCustomAttributes, 5); + }); + + test('static maxAttributeValueLength', () { + expect(HttpMetricPlatform.maxAttributeValueLength, 100); + }); + + test('throws if get httpResponseCode', () { + expect(() => httpMetricPlatform.httpResponseCode, throwsUnimplementedError); + }); + + test('throws if get requestPayloadSize', () { + expect( + () => httpMetricPlatform.requestPayloadSize, + throwsUnimplementedError, + ); + }); + + test('throws if get responseContentType', () { + expect( + () => httpMetricPlatform.responseContentType, + throwsUnimplementedError, + ); + }); + + test('throws if get responsePayloadSize', () { + expect( + () => httpMetricPlatform.responsePayloadSize, + throwsUnimplementedError, + ); + }); + + test('throws if set httpResponseCode', () { + expect( + () => httpMetricPlatform.httpResponseCode = 4, + throwsUnimplementedError, + ); + }); + + test('throws if set requestPayloadSize', () { + expect( + () => httpMetricPlatform.requestPayloadSize = 4, + throwsUnimplementedError, + ); + }); + + test('throws if set responsePayloadSize', () { + expect( + () => httpMetricPlatform.responsePayloadSize = 4, + throwsUnimplementedError, + ); + }); + + test('throws if set responseContentType', () { + expect( + () => httpMetricPlatform.responseContentType = 'foo', + throwsUnimplementedError, + ); + }); + + test('throws if start()', () { + expect(() => httpMetricPlatform.start(), throwsUnimplementedError); + }); + + test('throws if stop()', () { + expect(() => httpMetricPlatform.stop(), throwsUnimplementedError); + }); + + test('throws if putAttribute()', () { + expect( + () => httpMetricPlatform.putAttribute('foo', 'baz'), + throwsUnimplementedError, + ); + }); + + test('throws if removeAttribute()', () { + expect( + () => httpMetricPlatform.removeAttribute('bar'), + throwsUnimplementedError, + ); + }); + + test('throws if getAttribute()', () { + expect( + () => httpMetricPlatform.getAttribute('bar'), + throwsUnimplementedError, + ); + }); + + test('throws if getAttributes()', () { + expect(() => httpMetricPlatform.getAttributes(), throwsUnimplementedError); + }); +} + +class TestHttpMetricPlatform extends HttpMetricPlatform { + TestHttpMetricPlatform() : super(); +} diff --git a/packages/firebase_performance/firebase_performance_platform_interface/test/platform_interface_tests/platform_interface_trace_test.dart b/packages/firebase_performance/firebase_performance_platform_interface/test/platform_interface_tests/platform_interface_trace_test.dart new file mode 100644 index 000000000000..5c461c18f3a5 --- /dev/null +++ b/packages/firebase_performance/firebase_performance_platform_interface/test/platform_interface_tests/platform_interface_trace_test.dart @@ -0,0 +1,89 @@ +// Copyright 2021, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_performance_platform_interface/firebase_performance_platform_interface.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import '../mock.dart'; + +void main() { + setupFirebasePerformanceMocks(); + + late TestHttpTracePlatform tracePlatform; + + group('$HttpMetricPlatform()', () { + setUpAll(() async { + await Firebase.initializeApp(); + + tracePlatform = TestHttpTracePlatform(); + }); + }); + test('Constructor', () { + expect(tracePlatform, isA()); + expect(tracePlatform, isA()); + }); + + test('static maxAttributeKeyLength', () { + expect(HttpMetricPlatform.maxAttributeKeyLength, 40); + }); + + test('static maxCustomAttributes', () { + expect(HttpMetricPlatform.maxCustomAttributes, 5); + }); + + test('static maxAttributeValueLength', () { + expect(HttpMetricPlatform.maxAttributeValueLength, 100); + }); + + test('throws if start()', () { + expect(() => tracePlatform.start(), throwsUnimplementedError); + }); + + test('throws if stop()', () { + expect(() => tracePlatform.stop(), throwsUnimplementedError); + }); + + test('throws if incrementMetric()', () { + expect( + () => tracePlatform.incrementMetric('foo', 99), + throwsUnimplementedError, + ); + }); + + test('throws if setMetric()', () { + expect(() => tracePlatform.setMetric('foo', 99), throwsUnimplementedError); + }); + + test('throws if getMetric()', () { + expect(() => tracePlatform.getMetric('foo'), throwsUnimplementedError); + }); + + test('throws if putAttribute()', () { + expect( + () => tracePlatform.putAttribute('foo', 'baz'), + throwsUnimplementedError, + ); + }); + + test('throws if removeAttribute()', () { + expect( + () => tracePlatform.removeAttribute('bar'), + throwsUnimplementedError, + ); + }); + + test('throws if getAttribute()', () { + expect(() => tracePlatform.getAttribute('bar'), throwsUnimplementedError); + }); + + test('throws if getAttributes()', () { + expect(() => tracePlatform.getAttributes(), throwsUnimplementedError); + }); +} + +class TestHttpTracePlatform extends TracePlatform { + TestHttpTracePlatform() : super(); +} diff --git a/packages/firebase_performance/firebase_performance_web/lib/firebase_performance_web.dart b/packages/firebase_performance/firebase_performance_web/lib/firebase_performance_web.dart index 065fba3c0555..a6ae44239256 100644 --- a/packages/firebase_performance/firebase_performance_web/lib/firebase_performance_web.dart +++ b/packages/firebase_performance/firebase_performance_web/lib/firebase_performance_web.dart @@ -1,58 +1,77 @@ -import 'package:firebase/firebase.dart' as firebase; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/services.dart'; import 'package:firebase_core_web/firebase_core_web.dart'; + import 'package:firebase_performance_platform_interface/firebase_performance_platform_interface.dart'; import 'package:flutter/material.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; -import 'src/http_metric.dart'; import 'src/trace.dart'; +import 'src/interop/performance.dart' as performance_interop; /// Web implementation for [FirebasePerformancePlatform] class FirebasePerformanceWeb extends FirebasePerformancePlatform { + /// Stub initializer to allow the [registerWith] to create an instance without + /// registering the web delegates or listeners. + FirebasePerformanceWeb._() + : _webPerformance = null, + super(appInstance: null); + /// Instance of Performance from the web plugin. - firebase.Performance? _performance; + performance_interop.Performance? _webPerformance; /// Lazily initialize [_webRemoteConfig] on first method call - firebase.Performance get _delegate { - return _performance ??= firebase.performance(); + performance_interop.Performance get _delegate { + return _webPerformance ??= performance_interop.getPerformanceInstance(); } - /// A constructor that allows tests to override the firebase.Performance object. - FirebasePerformanceWeb({firebase.Performance? performance}) - : _performance = performance, - super(); + /// Builds an instance of [FirebasePerformanceWeb] + /// Performance web currently only supports the default app instance + FirebasePerformanceWeb() : super(); + + /// Initializes a stub instance to allow the class to be registered. + static FirebasePerformanceWeb get instance { + return FirebasePerformanceWeb._(); + } /// Called by PluginRegistry to register this plugin for Flutter Web static void registerWith(Registrar registrar) { FirebaseCoreWeb.registerService('performance'); - FirebasePerformancePlatform.instance = FirebasePerformanceWeb(); + FirebasePerformancePlatform.instance = FirebasePerformanceWeb.instance; + } + + @override + FirebasePerformancePlatform delegateFor({required FirebaseApp app}) { + return FirebasePerformanceWeb(); } @visibleForTesting - // ignore: use_setters_to_change_properties - static void registerWithForTest( - FirebasePerformancePlatform firebasePerformancePlatform, - ) { - FirebasePerformancePlatform.instance = firebasePerformancePlatform; + // ignore: avoid_setters_without_getters + set mockDelegate(performance_interop.Performance performance) { + _webPerformance = performance; } @override Future isPerformanceCollectionEnabled() async { - return true; + return _delegate.dataCollectionEnabled; } @override Future setPerformanceCollectionEnabled(bool enabled) async { - return; + _delegate.setPerformanceCollection(enabled); } @override TracePlatform newTrace(String name) { - return TraceWeb(_delegate.trace(name), name); + return TraceWeb(_delegate.trace(name)); } @override HttpMetricPlatform newHttpMetric(String url, HttpMethod httpMethod) { - return HttpMetricWeb('', HttpMethod.Get); + throw PlatformException( + code: 'non-existent', + message: + "Performance Web does not currently support 'HttpMetric' (custom network tracing).", + ); } } diff --git a/packages/firebase_performance/firebase_performance_web/lib/src/http_metric.dart b/packages/firebase_performance/firebase_performance_web/lib/src/http_metric.dart deleted file mode 100644 index db4b8e4cad8f..000000000000 --- a/packages/firebase_performance/firebase_performance_web/lib/src/http_metric.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:firebase_performance_platform_interface/firebase_performance_platform_interface.dart'; - -/// Web implementation for HttpMetricPlatform. Custom request metrics are not -/// supported for Web apps, so this class is a dummy. -class HttpMetricWeb extends HttpMetricPlatform { - HttpMetricWeb(String url, HttpMethod httpMethod) : super(url, httpMethod); - - /// HttpResponse code of the request. - @override - int? get httpResponseCode { - // ignore: avoid_returning_null - return null; - } - - /// Size of the request payload. - @override - int? get requestPayloadSize { - // ignore: avoid_returning_null - return null; - } - - /// Content type of the response such as text/html, application/json, etc... - @override - String? get responseContentType { - return null; - } - - /// Size of the response payload. - @override - int? get responsePayloadSize { - // ignore: avoid_returning_null - return null; - } - - @override - set httpResponseCode(int? httpResponseCode) { - return; - } - - @override - set requestPayloadSize(int? requestPayloadSize) { - return; - } - - @override - set responsePayloadSize(int? responsePayloadSize) { - return; - } - - @override - Future start() async { - return; - } - - @override - Future stop() async { - return; - } - - @override - Future putAttribute(String name, String value) async { - return; - } - - @override - Future removeAttribute(String name) async { - return; - } - - @override - String? getAttribute(String name) { - return null; - } - - @override - Future> getAttributes() async { - return {}; - } -} diff --git a/packages/firebase_performance/firebase_performance_web/lib/src/internals.dart b/packages/firebase_performance/firebase_performance_web/lib/src/internals.dart new file mode 100644 index 000000000000..283a9eda05ae --- /dev/null +++ b/packages/firebase_performance/firebase_performance_web/lib/src/internals.dart @@ -0,0 +1,19 @@ +// Copyright 2021 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. + +export 'package:firebase_core/src/internals.dart' hide guard; + +import 'package:firebase_core/firebase_core.dart'; +// ignore: implementation_imports +import 'package:firebase_core/src/internals.dart' as internals; + +/// Will return a [FirebaseException] from a thrown web error. +/// Any other errors will be propagated as normal. +Future guard(R Function() cb) async { + return internals.guard( + cb, + plugin: 'firebase_performance', + codeParser: (code) => code.replaceFirst('performance/', ''), + ); +} diff --git a/packages/firebase_performance/firebase_performance_web/lib/src/interop/firebase_interop.dart b/packages/firebase_performance/firebase_performance_web/lib/src/interop/firebase_interop.dart new file mode 100644 index 000000000000..720864274bce --- /dev/null +++ b/packages/firebase_performance/firebase_performance_web/lib/src/interop/firebase_interop.dart @@ -0,0 +1,14 @@ +// Copyright 2021, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +@JS('firebase') +library firebase_interop.firebase; + +import 'package:firebase_core_web/firebase_core_web_interop.dart'; + +import 'package:js/js.dart'; +import 'performance_interop.dart'; + +@JS() +external PerformanceJsImpl performance([AppJsImpl? app]); diff --git a/packages/firebase_performance/firebase_performance_web/lib/src/interop/performance.dart b/packages/firebase_performance/firebase_performance_web/lib/src/interop/performance.dart new file mode 100644 index 000000000000..99578d7c34b7 --- /dev/null +++ b/packages/firebase_performance/firebase_performance_web/lib/src/interop/performance.dart @@ -0,0 +1,87 @@ +// Copyright 2021, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_core_web/firebase_core_web_interop.dart' hide jsify; + +import 'firebase_interop.dart' as firebase_interop; +import 'performance_interop.dart' as performance_interop; + +/// Given an AppJSImp, return the Performance instance. Performance web +/// only works with the default app. +Performance getPerformanceInstance([App? app]) { + return Performance.getInstance(firebase_interop.performance()); +} + +class Performance + extends JsObjectWrapper { + static final _expando = Expando(); + + static Performance getInstance( + performance_interop.PerformanceJsImpl jsObject, + ) => + _expando[jsObject] ??= Performance._fromJsObject(jsObject); + + Performance._fromJsObject(performance_interop.PerformanceJsImpl jsObject) + : super.fromJsObject(jsObject); + + Trace trace(String traceName) => + Trace.fromJsObject(jsObject.trace(traceName)); + + /// Non-null App for this instance of firestore service. + App get app => App.getInstance(jsObject.app); + + bool get instrumentationEnabled => jsObject.instrumentationEnabled; + bool get dataCollectionEnabled => jsObject.dataCollectionEnabled; + + // ignore: use_setters_to_change_properties + void setPerformanceCollection(bool enableDataCollection) { + jsObject.dataCollectionEnabled = enableDataCollection; + } + + // ignore: use_setters_to_change_properties + void setInstrumentation(bool enableInstrumentation) { + jsObject.instrumentationEnabled = enableInstrumentation; + } +} + +class Trace extends JsObjectWrapper { + Trace.fromJsObject(performance_interop.TraceJsImpl jsObject) + : super.fromJsObject(jsObject); + + String getAttribute(String attr) => jsObject.getAttribute(attr); + + Map getAttributes() { + return dartify(jsObject.getAttributes()).cast(); + } + + int getMetric(String metricName) => jsObject.getMetric(metricName); + + void incrementMetric(String metricName, [int? num]) { + if (num != null) { + return jsObject.incrementMetric(metricName, num); + } else { + return jsObject.incrementMetric(metricName); + } + } + + void putMetric(String metricName, int num) { + return jsObject.putMetric(metricName, num); + } + + void putAttribute(String attr, String value) { + return jsObject.putAttribute(attr, value); + } + + void removeAttribute(String attr) { + return jsObject.removeAttribute(attr); + } + + void start() { + return jsObject.start(); + } + + void stop() { + return jsObject.stop(); + } +} diff --git a/packages/firebase_performance/firebase_performance_web/lib/src/interop/performance_interop.dart b/packages/firebase_performance/firebase_performance_web/lib/src/interop/performance_interop.dart new file mode 100644 index 000000000000..c4ecdac4124f --- /dev/null +++ b/packages/firebase_performance/firebase_performance_web/lib/src/interop/performance_interop.dart @@ -0,0 +1,31 @@ +// Copyright 2021, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +@JS('firebase.performance') +library firebase.performance_interop; + +import 'package:firebase_core_web/firebase_core_web_interop.dart'; +import 'package:js/js.dart'; + +@JS('Performance') +abstract class PerformanceJsImpl { + external AppJsImpl get app; + external TraceJsImpl trace(String traceName); + external bool dataCollectionEnabled; + external bool instrumentationEnabled; +} + +@JS('Trace') +@anonymous +class TraceJsImpl { + external String getAttribute(String attr); + external Object getAttributes(); + external int getMetric(String metricName); + external void incrementMetric(String metricName, [int? num]); + external void putMetric(String metricName, [int num]); + external void putAttribute(String attr, String value); + external void removeAttribute(String attr); + external void start(); + external void stop(); +} diff --git a/packages/firebase_performance/firebase_performance_web/lib/src/trace.dart b/packages/firebase_performance/firebase_performance_web/lib/src/trace.dart index 54c5e5299216..c98ac8211efd 100644 --- a/packages/firebase_performance/firebase_performance_web/lib/src/trace.dart +++ b/packages/firebase_performance/firebase_performance_web/lib/src/trace.dart @@ -1,44 +1,49 @@ -import 'package:firebase/firebase.dart' as firebase; +// Copyright 2021 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 'package:firebase_performance_platform_interface/firebase_performance_platform_interface.dart'; +import 'interop/performance.dart' as performance_interop; +import 'internals.dart'; /// Web implementation for TracePlatform. class TraceWeb extends TracePlatform { - final firebase.Trace traceDelegate; + final performance_interop.Trace traceDelegate; - TraceWeb(this.traceDelegate, String name) : super(name); + TraceWeb(this.traceDelegate) : super(); @override Future start() async { - traceDelegate.start(); + await guard(traceDelegate.start); } @override Future stop() async { - traceDelegate.stop(); + await guard(traceDelegate.stop); } @override - Future incrementMetric(String name, int value) async { + void incrementMetric(String name, int value) { traceDelegate.incrementMetric(name, value); } @override - Future setMetric(String name, int value) async { - return; + void setMetric(String name, int value) { + traceDelegate.putMetric(name, value); } @override - Future getMetric(String name) async { + int getMetric(String name) { return traceDelegate.getMetric(name); } @override - Future putAttribute(String name, String value) async { + void putAttribute(String name, String value) { traceDelegate.putAttribute(name, value); } @override - Future removeAttribute(String name) async { + void removeAttribute(String name) { traceDelegate.removeAttribute(name); } @@ -48,7 +53,7 @@ class TraceWeb extends TracePlatform { } @override - Future> getAttributes() async { - return traceDelegate.getAttributes().cast(); + Map getAttributes() { + return traceDelegate.getAttributes(); } } diff --git a/packages/firebase_performance/firebase_performance_web/pubspec.yaml b/packages/firebase_performance/firebase_performance_web/pubspec.yaml index 23c14bf67fa7..d2bbe52d8d11 100644 --- a/packages/firebase_performance/firebase_performance_web/pubspec.yaml +++ b/packages/firebase_performance/firebase_performance_web/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter + js: ^0.6.3 dev_dependencies: flutter_test: diff --git a/packages/firebase_performance/firebase_performance_web/test/firebase_performance_web_test.dart b/packages/firebase_performance/firebase_performance_web/test/firebase_performance_web_test.dart index 6a9e1efcd1e1..6bfbf5f5a6dd 100644 --- a/packages/firebase_performance/firebase_performance_web/test/firebase_performance_web_test.dart +++ b/packages/firebase_performance/firebase_performance_web/test/firebase_performance_web_test.dart @@ -1,9 +1,8 @@ @TestOn('chrome') // Uses web-only Flutter SDK -import 'package:firebase/firebase.dart'; import 'package:firebase_performance_platform_interface/firebase_performance_platform_interface.dart'; import 'package:firebase_performance_web/firebase_performance_web.dart'; -import 'package:firebase_performance_web/src/http_metric.dart'; +import 'package:firebase_performance_web/src/interop/performance.dart'; import 'package:firebase_performance_web/src/trace.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; @@ -14,26 +13,20 @@ class MockTrace extends Mock implements Trace {} void main() { group('FirebasePerformanceWeb', () { - late FirebasePerformancePlatform firebasePerformancePlatform; + late FirebasePerformanceWeb firebasePerformancePlatform; late MockPerformance mockPerformance; setUp(() { mockPerformance = MockPerformance(); - firebasePerformancePlatform = - FirebasePerformanceWeb(performance: mockPerformance); + firebasePerformancePlatform = FirebasePerformanceWeb(); + firebasePerformancePlatform.mockDelegate = mockPerformance; }); - test('isPerformanceCollectionEnabled always returns true', () async { - expect( - await firebasePerformancePlatform.isPerformanceCollectionEnabled(), - true, + test('isPerformanceCollectionEnabled', () async { + await expectLater( + firebasePerformancePlatform.setPerformanceCollectionEnabled(true), + completes, ); - verifyNoMoreInteractions(mockPerformance); - }); - - test('setPerformanceCollectionEnabled does nothing', () async { - await firebasePerformancePlatform.setPerformanceCollectionEnabled(true); - verifyNoMoreInteractions(mockPerformance); }); test('newTrace returns correct trace web platform object', () async { @@ -46,52 +39,18 @@ void main() { expect(trace.runtimeType, TraceWeb); trace = trace as TraceWeb; expect(trace.traceDelegate, mockTrace); - expect(trace.name, testTraceName); verify(mockPerformance.trace(testTraceName)).called(1); verifyNoMoreInteractions(mockPerformance); verifyNoMoreInteractions(mockTrace); }); - - test('newHttpMetric returns a dummy object', () async { - HttpMetricPlatform httpMeric = firebasePerformancePlatform.newHttpMetric( - 'http://test_url', - HttpMethod.Get, - ); - - expect(httpMeric.runtimeType, HttpMetricWeb); - expect(httpMeric.url, ''); - verifyNoMoreInteractions(mockPerformance); - }); - - test('startTrace starts a trace and returns the trace web platform object', - () async { - const testTraceName = 'test_trace'; - final MockTrace mockTrace = MockTrace(); - when(mockPerformance.trace(testTraceName)).thenReturn(mockTrace); - FirebasePerformanceWeb.registerWithForTest(firebasePerformancePlatform); - - TracePlatform trace = - await FirebasePerformancePlatform.startTrace(testTraceName); - - expect(trace.runtimeType, TraceWeb); - trace = trace as TraceWeb; - expect(trace.traceDelegate, mockTrace); - expect(trace.name, testTraceName); - verify(mockPerformance.trace(testTraceName)).called(1); - verify(mockTrace.start()).called(1); - verifyNoMoreInteractions(mockPerformance); - verifyNoMoreInteractions(mockTrace); - }); }); - group('TraceWeb', () { late TracePlatform tracePlatform; late MockTrace mockTrace; - late String testTraceName = 'test_trace'; setUp(() { mockTrace = MockTrace(); - tracePlatform = TraceWeb(mockTrace, testTraceName); + tracePlatform = TraceWeb(mockTrace); }); test('start', () async { @@ -106,31 +65,32 @@ void main() { verifyNoMoreInteractions(mockTrace); }); - test('incrementMetric', () async { - await tracePlatform.incrementMetric('counter_name', 33); + test('incrementMetric', () { + tracePlatform.incrementMetric('counter_name', 33); verify(mockTrace.incrementMetric('counter_name', 33)).called(1); verifyNoMoreInteractions(mockTrace); }); - test('setMetric does nothing', () async { - await tracePlatform.setMetric('counter_name', 33); + test('setMetric', () { + tracePlatform.setMetric('set_name', 50); + verify(mockTrace.putMetric('set_name', 50)).called(1); verifyNoMoreInteractions(mockTrace); }); test('getMetric', () async { - await tracePlatform.getMetric('counter_name'); + tracePlatform.getMetric('counter_name'); verify(mockTrace.getMetric('counter_name')).called(1); verifyNoMoreInteractions(mockTrace); }); - test('putAttribute', () async { - await tracePlatform.putAttribute('attribute', 'value'); + test('putAttribute', () { + tracePlatform.putAttribute('attribute', 'value'); verify(mockTrace.putAttribute('attribute', 'value')).called(1); verifyNoMoreInteractions(mockTrace); }); - test('removeAttribute', () async { - await tracePlatform.removeAttribute('attribute'); + test('removeAttribute', () { + tracePlatform.removeAttribute('attribute'); verify(mockTrace.removeAttribute('attribute')).called(1); verifyNoMoreInteractions(mockTrace); }); @@ -143,38 +103,9 @@ void main() { test('getAttributes', () async { when(mockTrace.getAttributes()).thenReturn({}); - await tracePlatform.getAttributes(); + tracePlatform.getAttributes(); verify(mockTrace.getAttributes()).called(1); verifyNoMoreInteractions(mockTrace); }); }); - - group('HttpMetricWeb', () { - late HttpMetricPlatform httpMetricPlatform; - - setUp(() { - httpMetricPlatform = HttpMetricWeb('', HttpMethod.Get); - }); - - test('httpResponseCode setter does nothing', () async { - httpMetricPlatform.httpResponseCode = 404; - expect(httpMetricPlatform.httpResponseCode, null); - }); - - test('requestPayloadSize setter does nothing', () async { - httpMetricPlatform.requestPayloadSize = 100; - expect(httpMetricPlatform.requestPayloadSize, null); - }); - - test('responsePayloadSize setter does nothing', () async { - httpMetricPlatform.responsePayloadSize = 100; - expect(httpMetricPlatform.responsePayloadSize, null); - }); - - test('putAttribute does nothing', () async { - await httpMetricPlatform.putAttribute('attribute', 'value'); - expect(httpMetricPlatform.getAttribute('attribute'), null); - expect(await httpMetricPlatform.getAttributes(), {}); - }); - }); }