Download PDF
Download page Customize the Flutter Instrumentation.
Customize the Flutter Instrumentation
Once you have instrumented your Flutter application, you can make further manual customizations to the instrumentation.
Network Requests
To collect metrics on network requests, you can use the HTTP client wrapper:
DART
import 'package:http/http.dart' as http;
try {
final client = TrackedHttpClient(http.Client());
final response = await client.get(Uri.parse("https://www.appdynamics.com"));
print(response);
} catch (e) {
print(e);
}
If you need more granular control over request tracking, such as adding user data to the request, you can manually create a tracker object that will register request information on completion:
try {
final url = "www.appdynamics.com"
final tracker = await RequestTracker.create(url);
final response = await http.get(url);
await tracker.setResponseStatusCode(response.statusCode)
..setRequestHeaders(response.request!.headers)
..setResponseHeaders(response.headers)
..setUserDataBool("shouldRespond", false);
} catch (e) {
await tracker.setError(e.toString());
} finally {
await tracker.reportDone();
}
Add Server Correlation Headers
If another AppDynamics Agent is also used to instrument the backend of the application, you can add server correlation headers to pinpoint business transactions:
import 'package:http/http.dart' as http;
try {
final headers = await RequestTracker.getServerCorrelationHeaders();
final url = Uri.parse("https://www.appdynamics.com");
final client = HttpClient(http.Client());
final response = await client.get(url, headers: headers);
print(response);
} catch (e) {
print(e);
}
Methods
RequestTracker.create(url)
Returns a RequestTracker
object to be configured for manually tracking an HTTP request by providing a non-null url
(after calling Instrumentation.start()
).
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
url | String | Non-null. | The target URL of the request that will be tracked. |
setError(message, stackTrace)
Sets an error message describing the failure to receive a response (if such a failure occurred). If the request was successful, this method should not be called. Additional stackTrace strings can be added.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
message | String | Request did not finish successfully. | The error message or a string describing the error. |
stackTrace | String | Request did not finish successfully. | The error stackTrace property (if any). |
setResponseStatusCode(statusCode)
Sets the status code of the response (if any). If a response was received, this method should be an integer. If an error occurred and a response was not received, this method should not be called.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
| Integer | Request finished successfully. | The status code of the response. |
setRequestHeaders(requestHeaders)
Adds the request headers to the tracker object. If an error occurred and a response was not received, this method should not be called.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
| Map<String, String> | Request finished successfully. | A map of key-value pairs representing the request's header values. |
setResponseHeaders(responseHeaders)
Adds the request response to the tracker object. If an error occurred and a response was not received, this should not be called.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
| Map<String, String> | Request finished successfully. | A map of key-value pairs representing the response's header values. |
reportDone()
Signals that the HTTP request has finished and logged the appropriate information. If another request needs tracking, a new RequestTracker
should be created.
getServerCorrelationHeaders()
Returns the server correlation headers to be used if an AppDynamics Agent is also used on the server-side. Headers should be appended on the tracked request.
setUserdata
You an append custom values to the request tracking object with each corresponding API:
setUserData(key, value)
setUserDataBool(key, value)
setUserDataDateTime(key, value)
setUserDataDouble(key, value)
setUserDataInt(key, value)
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
| String | Max. 2048 characters. Unique (or will be overwritten). | Key name for the corresponding value. |
value | String, bool, DateTime, double, int | Max. 2048 characters. | Custom info appended to the request. |
Crashes
Report Native Crashes
The Flutter Agent automatically logs native crash information. This is already enabled when calling Instrumentation.start()
. Native crashes can be symbolicated passing the corresponding .dsym or ProGuard files in the UI Controller. See Get Human-Readable Crash Snapshots and Crash Snapshots.
Report All Crashes
For capturing all errors (Flutter and native), use zones:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:appdynamics_mobilesdk/appdynamics_mobilesdk.dart';
void main() {
runZonedGuarded(() {
WidgetsFlutterBinding.ensureInitialized();
FlutterError.onError = Instrumentation.errorHandler;
runApp(MyApp());
}, (Object error, StackTrace stack) async {
final details =
FlutterErrorDetails(exception: error.toString(), stack: stack);
await Instrumentation.errorHandler(details);
});
Report Dart Exceptions
Use the error handler provided by the Flutter Agent to intercept Dart exceptions and send them to the Controller:
import 'package:flutter/material.dart';
import 'package:appdynamics_mobilesdk/appdynamics_mobilesdk.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
FlutterError.onError = Instrumentation.errorHandler;
runApp(MyApp());
}
Disable Crash Reporting
Crash reporting is enabled by default. Most applications should leave crash reporting enabled, but you can disable it only if issues occur from using a different crash reporting tool.
You can enable or disable the native crash reporting feature in the AgentConfiguration
object:
AgentConfiguration config = AgentConfiguration(
appKey: <EUM_APP_KEY>,
crashReportingEnabled: false);
await Instrumentation.start(config);
Crash Callback
The Flutter Agent supports using a callback function that will receive a crash report. The report contains an array of the Dart objects representing native crash information.
The callback function can be configured in the AgentConfiguration
object:
void crashReportCallback(List<CrashReportSummary> summaries) async {
print(summaries);
}
AgentConfiguration config = AgentConfiguration(
appKey: <EUM_APP_KEY>,
crashReportCallback: crashReportCallback);
await Instrumentation.start(config);
Session Frames
Session frames provide context for what the user is doing during a session. You can either create custom user interaction frames in the Controller UI or in the SessionFrame API, in the agent instrumentation.
Some use cases for customizing session frames include:
- One page performs multiple functions, and you want more granular tracking of the individual functions.
- A user flow spans multiple pages or user interactions, and you want to categorize frames within a user flow. For example, if you have a user flow for purchasing, you could create session frames named "Login," "Product Selection," and "Purchase."
- You want to capture dynamic information based on user interactions to name session frames, such as an order ID.
Methods
Instrumentation.startSessionFrame(sessionFrameName)
Starts and returns a SessionFrame
object.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
sessionFrameName | String | Non-null | A descriptive name for the session frame that will be captured. |
updateName(newName)
Updates the session frame with a new name. This is generally used when the appropriate session frame name is not known when it was created.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
newName | String | Non-null | A descriptive name for the session frame that will be captured. |
end()
Reports the end of the session frame. The SessionFrame
object will no longer be usable after this call.
This is an example of how to mark the end of a current shopping session when a user presses the checkout button:
class ShoppingCart {
late SessionFrame _sessionFrame;
late String _orderId;
void onCheckoutCartButtonClick() async {
// The checkout starts when the user clicks the checkout button.
// This may be after they have updated quantities of items in their
// cart, etc.
_sessionFrame = await Instrumentation.startSessionFrame("Checkout");
}
void onConfirmOrderButtonClick() {
// Once they have confirmed payment info and shipping information,
// and they are clicking the "Confirm" button to start the backend
// process of checking out, we may know more information about the
// order itself, such as an order ID.
_sessionFrame.updateName("Checkout: Order ID {this.orderId}");
}
void onProcessOrderComplete() {
// Once the order is processed, the user is done "checking out" so we end
// the session frame.
_sessionFrame.end();
}
void onCheckoutCancel() {
// If they cancel or go back, you'll want to end the session frame also, or
// else it will be left open and appear to have never ended.
_sessionFrame.end();
}
Metrics
You can associate numeric values with a metric name and report them via Instrumentation.reportMetric
.
Methods
Instrumentation.reportMetric(name, value)
Reports a custom metric.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
| String | Only alphanumeric characters and spaces. | Representative name for the metric. |
value | Integer | Numeric, non-null. | The metric value. |
This is an example of how to track the number of times users click the checkout button:
import 'package:appdynamics_mobilesdk/appdynamics_mobilesdk.dart';
import 'package:flutter/material.dart';
class App extends StatelessWidget {
_finishCheckout() {
Instrumentation.reportMetric(name: "Checkout Count", value: 1);
// ...rest of the checkout logic
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Checkout screen")),
body: Center(
child:
ElevatedButton(
child: Text('Checkout'),
onPressed: _finishCheckout,
)
));
}
Automatic Screenshots (iOS Only)
By default, screenshot capturing is enabled, but if you need to disable it (such as for privacy reasons), you can disable it in the AgentConfiguration
object.
This is an example to disable screenshots:
AgentConfiguration config = AgentConfiguration(
appKey: <EUM_APP_KEY>,
screenshotsEnabled: false);
await Instrumentation.start(config);
User Data
You can set key-value pair identifiers for custom user data that will be included in all snapshots. Some considerations:
- The key must be unique across your application.
- The key and the associated value are limited to 2048 characters.
- A
null
value will clear the data, and re-using the same key overwrites the previous value. - The custom user data is not persisted across application runs. Once the application is destroyed, user data is cleared.
Methods
setUserData(key, value)
Used to add the string
types.
removeUserData(key)
Removes the string
corresponding to the key set with setUserData()
.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
key | String | Maximum 2048 characters (non-null). | The name of the user data key. |
value | String | Maximum 2048 characters. | The associated s |
setUserDataDouble(key, value)
Used to add the double
types.
removeUserDataDouble(key)
Removes the double
corresponding to the key set with setUserDataDouble()
.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
key | String | Maximum 2048 characters (non-null). | The name of the user data key. |
value | Double | Maximum 2048 characters. | The associated double value for the key. |
setUserDataInt(key, value)
Used to add the int
types.
removeUserDataInt(key)
Removes the int
corresponding to the key set with setUserDataInt()
.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
key | String | Max. 2048 characters, non-null. | The name of the user data key. |
value | Integer | Max. 2048 characters. | The associated int value for the key. |
setUserDataBool(key, value)
Used to add the bool
types.
removeUserDataBool(key)
Removes the bool
corresponding to the key set with setUserDataBool()
.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
key | String | Max. 2048 characters, non-null. | The name of the user data key. |
value | Boolean | - | The associated bool value for the key. |
setUserDataDateTime(key, value)
Used to add the DateTime
types.
removeUserDataDateTime(key)
Removes the DateTime
corresponding to the key set with setUserDataDateTime()
.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
key | String | Max. 2048 characters, non-null. | The name of the user data key. |
value | DateTime | - | The associated DateTime value for the key. |
This is an example of how to store a DateTime
object as user data:
await Instrumentation.setUserDataDateTime("currentTransactionYear", DateTime.utc(2021));
Breadcrumbs
You can report points of interest throughout the application via breadcrumbs. Breadcrumbs will be included in different reports depending on the mode:
- Use the
crashesOnly
mode (default) when you want breadcrumbs to appear only in crash reports (triggered on application restart). - Use the
crashesAndSessions
mode if you also want breadcrumbs to appear on the session.
Each crash report will display only the most recent 99 breadcrumbs.
Methods
leaveBreadcrumb(breadcrumb, mode)
Sets the name of the breadcrumb and where it appears on the mobile session.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
breadcrumb | String | Max. 2048 characters (every extra character will be truncated). | A descriptive string for the breadcrumb. |
mode
| BreadcrumbVisibility (enum) | - | Configuring whether to see breadcrumb info on the session. |
This is an example of how to leave breadcrumbs before and after switching to the sign-up screen:
Future<void> showSignUp() async {
try {
await Instrumentation.leaveBreadcrumb("Sign up button pressed.",
BreadcrumbVisibility.crashesAndSessions);
await pushSignUpScreen();
await Instrumentation.leaveBreadcrumb("Sign Up screen pushed",
BreadcrumbVisibility.crashesOnly);
} catch (e) {
...
}
}
Timers
You can time events by using customer timers that span across multiple methods.
Methods
startTimer(name)
Sets the timer.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
name | String | Only alphanumeric characters and spaces (non-null). | A unique identifier to correlate with the timer. |
stopTimer(name)
Stops the timer.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
name | String | Only alphanumeric characters and spaces (non-null). | A unique identifier to correlate with the timer. |
This is an example of how to start a timer to count time spent on checkout:
Future<void> doCheckout() async {
final checkoutTimerName = "Time spent on checkout";
try {
await Instrumentation.startTimer(checkoutTimerName);
await someCheckoutService();
await someOtherLongTask();
} finally {
await Instrumentation.stopTimer(checkoutTimerName);
}
}
Errors
You can report Dart errors, exceptions, and custom messages.
There are three error severity levels:
info
: an error occurred, but it did not cause a problem.warning
: an error occurred, but the application recovered gracefully.critical
: an error occurred and caused problems to the app.
Methods
reportException(exception, severityLevel)
Sets an error exception and the associated severity level.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
exception | Exception | - | Dart |
severityLevel | ErrorSeverityLevel (enum) | Default: warning | The corresponding level of severity. |
reportError(error, severityLevel)
Creates an error report with an associated severity level.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
error | Error | - | Dart |
severityLevel | ErrorSeverityLevel (enum) | Default: warning | The corresponding level of severity. |
reportMessage(message, severityLevel)
Creates a message report (should be used if neither reportError()
nor reportException()
are suitable.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
message | String | - | Dart |
severityLevel | ErrorSeverityLevel (enum) | Default: warning | The corresponding level of severity. |
This is an example of how to report NoSuchMethodError
when calling an undeclared method:
try {
final myMethod = null;
myMethod();
} on NoSuchMethodError catch (e) {
await Instrumentation.reportError(e, severityLevel: ErrorSeverityLevel.CRITICAL);
}
Application Not Responding (ANR)
You can configure automatic detection and reporting of cases when the application is not responding (ANR) when creating the AgentConfiguration
object.
This is an example of how to enable the automatic ANR detection feature:
AgentConfiguration config = AgentConfiguration(
appKey: <EUM_APP_KEY>
);
await Instrumentation.start(config);
Info Points
You can track specific points of interest in the app by using the Instrumentation
class.
Method
trackCall(className, methodName, methodBody, methodArgs, uniqueCallId)
Sets a tracker for a call.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
className | String | - | Name of the class or object of the method call. |
methodName
| String | - | Name of the method. |
methodBody | FutureOr<T> Function() | - | The body of the method that is being tracked. |
methodArgs | dynamic | - | The arguments of the method. |
uniqueCallId | String? | - | A unique custom ID for tracking this call. |
This is an example of tracking an asynchronous method that updates the users' first and last names on the server:
const firstName = "John";
const lastName = "Peters";
final result = await Instrumentation.trackCall(
className: "MyAccountScreen",
methodName: "updateUserName",
methodArgs: [firstName, lastName],
methodBody: () async {
await http.post(
Uri.parse('https://jsonplaceholder.typicode.com/albums'),
body: jsonEncode(
<String, String>{'firstName': firstName, 'lastName': lastName}),
);
}
);
Sessions
You can start a new session and mark the end of the current one.
Method
startNextSession()
Sets the beginning and end of a session.
This is an example of marking a new customer checkout session after the old one has ended:
Future<void> checkout(dynamic data) async {
try {
final response = http.post("https://server.com/checkout", data);
await Instrumentation.startNextSession();
} catch (e) {
print(e);
}
}
App Key
You can change the app key after initialization. Older reports will be discarded when a new key is applied. Invoking this method has no effect unless the agent was already initialized by calling one of the start methods.
Method
changeAppKey(newKey)
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
newKey | String | Must respect app key format (must be valid). | A new key that will replace the old one. |
This is an example of updating the app key to AA-BBB-CCC.
try {
await Instrumentation.changeAppKey("AA-BBB-CCC");
} catch (e) {
print(e);
}
Screen Tracking
You can add the NavigationObserver
to your app to automatically detect app widget transitions using named routes. You can manually track screens via the WidgetTracker
class if your app isn't implementing named routes.
Methods
NavigationObserver
import 'package:appdynamics_mobilesdk/appdynamics_mobilesdk.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: MyRoutes.mainScreen,
onGenerateRoute: MyRouter.onGenerateRoute,
navigatorObservers: [NavigationObserver()]);
}
}
WidgetTracker
You can add tracking to Flutter widgets to monitor a specific UI element. For example, if your Flutter app isn't automatically implementing named routes, you could manually track screens via the WidgetTracker
class.
Methods
trackWidgetStart()
Starts tracking a widget.
trackWidgetEnd()
Stops tracking a widget.
Parameters
Name | Type | Requirements | Description |
---|---|---|---|
widgetName | String | Must be unique. | Name of the class or object of the method call. |
This is an example of tracking the widget CheckoutPage.screenName
.
import 'package:appdynamics_mobilesdk/appdynamics_mobilesdk.dart';
import 'package:flutter/material.dart';
class CheckoutPage extends StatefulWidget {
const CheckoutPage({Key? key}) : super(key: key);
static String screenName = "Checkout Page";
@override
_CheckoutPageState createState() => _CheckoutPageState();
}
class _CheckoutPageState extends State<CheckoutPage> {
@override
void initState() async {
super.initState();
await WidgetTracker.instance.trackWidgetStart(CheckoutPage.screenName);
}
_backToMainScreen() async {
await WidgetTracker.instance.trackWidgetEnd(CheckoutPage.screenName);
Navigator.pop(context);
}
@override
Widget build(BuildContext context) {
return Center(
child:
Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
ElevatedButton(
child: const Text('Proceed'),
onPressed: _backToMainScreen,
)
]));
}
}
Device Metrics
Various metrics regarding device status are automatically tracked. See Mobile Device Metric Collection.
Flutter Agent API Documentation
See the latest Flutter Agent API documentation or the previous versions listed below: