newrelic_mobile 1.1.13 copy "newrelic_mobile: ^1.1.13" to clipboard
newrelic_mobile: ^1.1.13 copied to clipboard

Flutter plugin for NewRelic Mobile. This plugin allows you to instrument Flutter apps with help of native New Relic Android and iOS agents.

example/lib/main.dart

/*
 * Copyright (c) 2022-present New Relic Corporation. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:newrelic_mobile/loglevel.dart';
import 'package:newrelic_mobile/metricunit.dart';
import 'package:newrelic_mobile/config.dart';
import 'package:newrelic_mobile/network_failure.dart';
import 'package:newrelic_mobile/newrelic_mobile.dart';
import 'package:newrelic_mobile/newrelic_navigation_observer.dart';
import 'package:newrelic_mobile_example/app_config.dart';
import 'package:http/http.dart' as http;

const String readCounters = """
   query (\$id: Int) { # Define which variables will be used in the query (id)
  Media (id: \$id, type: ANIME) { # Insert our variables into the query arguments (id) (type: ANIME is hard-coded in the query)
    id
    title {
      romaji
      english
      native
    }
  }
}
""";

Future<void> main() async {
  await NewRelicService.initializeNewRelic(() {
    runApp(
      const MyApp(),
    );
  });
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Retail',
      localizationsDelegates: const [],
      supportedLocales: const [
        Locale('en'),
        Locale('es'),
        Locale('fr'),
      ],
    );
  }
}

class MyHttpOverrides extends HttpOverrides {
  @override
  HttpClient createHttpClient(SecurityContext? context) {
    return super.createHttpClient(context)
      ..badCertificateCallback = (X509Certificate cert, String host, int port) {
        return true;
      };
  }
}

// In a seperate file, we have this:

class NewRelicService {
  static Future<void> initializeNewRelic(Function runApp) async {
    String appToken = "";

    Config config = Config(
      accessToken: "AA47299f5f05f37319adab84290101a7d5b953cd86-NRMA",
      // ANDROID SPECIFIC
      // Enable or disable the collection of analytics events such as user interactions.
      // This includes button taps, screen transitions, and other significant user actions.
      // Setting this to false will prevent New Relic from capturing such events automatically.
      analyticsEventEnabled: true,

      // Enable or disable reporting of network errors (e.g., 4xx, 5xx HTTP status codes)
      // as MobileRequestError events. This includes HTTP request failures, timeouts,
      // and server errors.
      // Useful for identifying failed API requests and backend issues.
      networkErrorRequestEnabled: true,

      // Enable or disable tracking of all successful HTTP requests as MobileRequest events.
      // This captures request URL, status code, response time, and other metadata.
      // It provides insights into API performance and helps identify slow endpoints.
      networkRequestEnabled: true,

      // Enable or disable automatic crash reporting.
      // When enabled, New Relic will automatically capture uncaught exceptions
      // and crashes, including stack traces, device info, and app state.
      crashReportingEnabled: true,

      // Enable or disable interaction tracing.
      // Interaction tracing records user interactions such as screen transitions,
      // button taps, and custom events. If disabled, these interactions will not
      // be captured, though the instrumentation will still occur in the background.
      interactionTracingEnabled: true,

      // Enable or disable the capture of HTTP response bodies for error traces.
      // This applies to HTTP requests that return error codes (e.g., 4xx, 5xx).
      // Be cautious with this setting, as it may capture sensitive data.
      httpResponseBodyCaptureEnabled: true,

      // Enable or disable agent logging for New Relic.
      // This includes debug, warning, and error logs generated by the SDK.
      // Set to true during development for troubleshooting; set to false in production
      // to minimize performance impact.
      loggingEnabled: true,

      // IOS SPECIFIC
      // Enable or disable WebView instrumentation.
      // When enabled, New Relic will automatically track web requests made from
      // WebView components. This includes network requests and page load performance.
      webViewInstrumentation: true,

      // Enable or disable capturing print statements as New Relic events.
      // When enabled, all print() calls in the app are sent as analytics events.
      // Useful for tracking logs without adding explicit New Relic event calls.
      printStatementAsEventsEnabled: true,

      // Enable or disable automatic HTTP instrumentation.
      // When enabled, New Relic will capture HTTP requests and their metadata.
      // This includes request URLs, status codes, response times, and error codes.
      httpInstrumentationEnabled: true,

      // Enable or disable reporting data to government endpoints for FedRAMP compliance.
      // Set to true only if your app must adhere to US government data regulations.
      fedRampEnabled: false,

      // Enable or disable offline data storage.
      // When enabled, New Relic will queue data locally if there is no internet connection
      // and send it once connectivity is restored. Useful for ensuring data continuity.
      offlineStorageEnabled: true,

      // IOS SPECIFIC
      // Enable or disable background reporting functionality.
      // When enabled, New Relic will continue to collect data even when the app is
      // running in the background. This may increase resource usage.
      backgroundReportingEnabled: false,

      // IOS SPECIFIC
      // Enable or disable the use of New Relic’s new event system for iOS.
      // The new event system provides more stable and consistent event tracking.
      // Set to true if you want to use the latest event tracking system.
      newEventSystemEnabled: false,

      // Enable or disable distributed tracing.
      // Distributed tracing allows tracking requests across multiple services,
      // enabling end-to-end visibility of complex transaction flows.
      // This is critical for identifying performance bottlenecks in microservice architectures.
      distributedTracingEnabled: true,
    );

    await NewrelicMobile.instance.start(config, runApp);
  }
}

/// The main app.
// class MyApp extends StatelessWidget {
//   /// Creates an [App].
//   const MyApp({Key? key}) : super(key: key);
//
//   @override
//   Widget build(BuildContext context) {
//     return MaterialApp(
//       navigatorObservers: [NewRelicNavigationObserver()],
//       routes: {
//         'pageone': (context) => const Page1Screen(),
//         'pagetwo': (context) => Page2Screen(),
//         'pagethree': (context) => const Page3Screen(),
//         'pagefour': (context) => const Page4Screen()
//       },
//       theme: ThemeData(
//         primarySwatch: Colors.blue,
//         visualDensity: VisualDensity.adaptivePlatformDensity,
//       ),
//       initialRoute: 'pageone',
//     );
//   }
// }

/// The screen of the first page.
class Page1Screen extends StatelessWidget {
  /// Creates a [Page1Screen].
  const Page1Screen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => GraphQLProvider(
        client: client,
        child: Scaffold(
          appBar: AppBar(title: const Text("Http Demo")),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                ElevatedButton(
                    onPressed: () async {
                      debugPrint(null);
                      NewrelicMobile.instance.recordMetric(
                        "Button Clicks",
                        "Test Champ",
                      );
                      NewrelicMobile.instance
                          .recordBreadcrumb("Button Got Pressed on Screen 3");
                      NewrelicMobile.instance.logInfo("testing logs");
                      NewrelicMobile.instance.logDebug("testing logs debug");
                      NewrelicMobile.instance
                          .logWarning("testing logs warning");
                      NewrelicMobile.instance
                          .logVerbose("testing logs verbose");
                      NewrelicMobile.instance
                          .log(LogLevel.ERROR, "testing logs");
                      NewrelicMobile.instance.logInfo("testing logs");
                      NewrelicMobile.instance.logInfo("testing logs");

                      // NewrelicMobile.instance.crashNow();

                      var map = {};
                      map["test12"] = "value";
                      map["test23"] = "value1";
                      map["logLevel"] = "INFO";
                      map["message"] = "testing logs with attributes";

                      NewrelicMobile.instance
                          .logAttributes(map.cast<String, dynamic>());

                      NewrelicMobile.instance
                          .logAll(Exception("This is an exception"), {
                        "BreadNumValue": 12.3,
                        "BreadStrValue": "FlutterBread",
                        "BreadBoolValue": true,
                        "message": "This is a message with attributes"
                      });

                      NewrelicMobile.instance.logAttributes({
                        "BreadNumValue": 12.3,
                        "BreadStrValue": "FlutterBread",
                        "BreadBoolValue": true,
                        "message": "This is a message with attributes"
                      });

                      if (kDebugMode) {
                        print(await NewrelicMobile.instance.currentSessionId());
                      }
                      // showDialog<String>(
                      //   barrierDismissible: false,
                      //   context: context,
                      //   builder: (BuildContext context) => const PopPopPop(),
                      // );
                      if (kDebugMode) {
                        print(NewrelicMobile.instance.currentSessionId());
                      }
                      NewrelicMobile.instance.incrementAttribute(
                          "FlutterCustomAttrNumber",
                          value: 5.0);
                      // NewrelicMobile.instance.recordMetric("testMetric", "Test Champ",value: 12.0);
                      NewrelicMobile.instance.recordMetric(
                          "testMetric1", "TestChamp12",
                          value: 10,
                          valueUnit: MetricUnit.BYTES,
                          countUnit: MetricUnit.PERCENT);
                    },
                    child: const Text('Test New Static Methods',
                        maxLines: 1, textDirection: TextDirection.ltr)),
                ElevatedButton(
                    onPressed: () async {
                      final client = HttpClient();
                      // Here can be any non-existing URL.
                      final request = await client
                          .postUrl(Uri.parse("https://localhost:8080"));
                      request.headers.set(HttpHeaders.contentTypeHeader,
                          "application/json; charset=UTF-8");
                      request.headers.set("ngrok-skip-browser-warning", 69420);
                      request.write(
                          '{"title": "Foo","body": "Bar", "userId": 99}');

                      final response = await request.close();

                      response.transform(utf8.decoder).listen((contents) {
                        if (kDebugMode) {
                          print(contents);
                        }
                      });
                    },
                    child: const Text('Http call to non-existing URL',
                        maxLines: 1, textDirection: TextDirection.ltr)),
                ElevatedButton(
                    onPressed: () async {
                      final client = HttpClient();
                      final request = await client.postUrl(Uri.parse(
                          "https://8f1d-2600-1006-b003-7627-ca1-491c-9b0-25ff.ngrok.io/notice_error"));
                      request.headers.set(HttpHeaders.contentTypeHeader,
                          "application/json; charset=UTF-8");
                      request.headers.set("Car", "Honda");
                      request.headers.set("ngrok-skip-browser-warning", 69420);
                      request.write(
                          '{"title": "Foo","body": "Bar", "userId": 99}');

                      final response = await request.close();

                      response.transform(utf8.decoder).listen((contents) {
                        if (kDebugMode) {
                          print(contents);
                        }
                      });
                    },
                    child: const Text('Http Default Client',
                        maxLines: 1, textDirection: TextDirection.ltr)),
                ElevatedButton(
                    onPressed: () async {
                      // final client = HttpClient();
                      // var uri = Uri.parse("https://reactnative.dev/movies.json");
                      var response = await http.get(
                          Uri.parse("https://reactnative.dev/movies.json"));
                      // request.followRedirects = false;

                      // var url = Uri.parse(
                      //     'http://graph.facebook.com/');
                      // var response = await http.get(url);
                      // print('Response status: ${await response.stream.bytesToString()}');
                      if (kDebugMode) {
                        print('Response body: ${response.statusCode}');
                      }
                    },
                    child: const Text('Http Library ',
                        maxLines: 1, textDirection: TextDirection.ltr)),
                ElevatedButton(
                    onPressed: () async {
                      try {
                        var dio = Dio();
                        dio.options.headers['Car'] = 'Toyota';
                        dio.options.followRedirects = false;
                        var response =
                            await dio.get('http://graph.facebook.com/');
                        if (kDebugMode) {
                          print(response);
                        }
                      } catch (e) {
                        if (kDebugMode) {
                          print(e);
                        }
                      }
                    },
                    child: const Text('Http Dio Library ',
                        maxLines: 1, textDirection: TextDirection.ltr)),
                ElevatedButton(
                    onPressed: () async {
                      try {
                        var dio = Dio();
                        var response = await dio
                            .get('https://reactnative.dev/movies.json');
                        if (kDebugMode) {
                          print(response);
                        }
                      } catch (e) {
                        if (kDebugMode) {
                          print(e);
                        }
                      }
                    },
                    child: const Text('OOM Issue Library ',
                        maxLines: 1, textDirection: TextDirection.ltr)),
                ElevatedButton(
                    onPressed: () async {
                      NewrelicMobile.instance.noticeNetworkFailure(
                          "https://cb6b02be-a319-4de5-a3b1-361de2564493.mock.pstmn.io/searchpage",
                          "GET",
                          1000,
                          2000,
                          NetworkFailure.dnsLookupFailed);
                    },
                    child: const Text('NetWork Failure',
                        maxLines: 1, textDirection: TextDirection.ltr)),
                ElevatedButton(
                    onPressed: () async {
                      try {
                        var dio = Dio();
                        var response = await dio.post(
                            'https://reqres.in/api/register',
                            data: "{ 'email': 'sydney@fife'}");
                        if (kDebugMode) {
                          print(response.data);
                        }
                      } catch (e) {
                        if (kDebugMode) {
                          print(e);
                        }
                      }
                    },
                    child: const Text('Http Dio Post Library ',
                        maxLines: 1, textDirection: TextDirection.ltr)),
                Query(
                    options: QueryOptions(document: gql(readCounters)),
                    builder: (result, {fetchMore, refetch}) {
                      if (kDebugMode) {
                        print(result.data.toString());
                      }
                      // If stements here to check handle different states;
                      if (result.isLoading) {
                        return const Center(
                          child: CircularProgressIndicator(),
                        );
                      }
                      return Text(result.data.toString());
                    }),
                Image.network('https://picsum.photos/250?image=9'),
                ElevatedButton(
                  onPressed: () async {
                    var id = await NewrelicMobile.instance
                        .startInteraction("Going to Page 2");
                    Future.delayed(const Duration(milliseconds: 100), () {
                      Navigator.pushNamed(context, 'pagetwo',
                          arguments: {'id': id});
                    });
                  },
                  child: const Text('Go to page 2'),
                ),
              ],
            ),
          ),
        ),
      );
}

/// The screen of the second page.
//ignore: must_be_immutable
class Page2Screen extends StatelessWidget {
  /// Creates a [Page2Screen].

  dynamic interActionId;

  Page2Screen({Key? key, this.interActionId}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context)!.settings.arguments as Map;

    NewrelicMobile.instance.endInteraction(args['id']);

    return Scaffold(
      appBar: AppBar(title: const Text("Error Demo")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: () {
                bar();
              },
              child: const Text('Async Error'),
            ),
            ElevatedButton(
              onPressed: () {
                foo();
              },
              child: const Text('Record Error with Attributes'),
            ),
            ElevatedButton(
              onPressed: () {
                throw StateError("State Error");
              },
              child: const Text('State Error'),
            ),
            const Row(
              children: [
                Text(
                  "ErrorErrorErrorErrorErrorErrorErrorErrorErrorErrorErrorErrorErrorErrorErrorErrorErrorErrorErrorErrorErrorError",
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                )
              ],
            ),
            ElevatedButton(
              onPressed: () {
                if (kDebugMode) {
                  print("test");
                }
                debugPrint("test");
                throw TypeError();
              },
              child: const Text('NullThrownError'),
            ),
            ElevatedButton(
              onPressed: () => Navigator.pushNamed(context, 'pagethree'),
              child: const Text('Go to home page'),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> foo() async {
    var bar = {};
    try {
      throw bar['name'];
    } catch (error) {
      Map<String, dynamic> attributes = {
        "error attribute": "12344",
        "error test attribute": 1234
      };
      NewrelicMobile.instance
          .recordError(error, StackTrace.current, attributes: attributes);
    }
  }

  bar() {
    Future(() {
      throw "asynchronous error";
    });
  }
}

class Page3Screen extends StatelessWidget {
  /// Creates a [Page2Screen].
  const Page3Screen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(title: const Text("Page 3")),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              ElevatedButton(
                onPressed: () {
                  for (var i = 0; i < 100; i++) {
                    NewrelicMobile.instance.recordCustomEvent(
                        "Test Custom Event",
                        eventName: "User Purchase",
                        eventAttributes: {
                          "item1": "Clothes",
                          "price": 34.00,
                          "loop test": i
                        });
                  }
                },
                child: const Text('Record Custom Event'),
              ),
              ElevatedButton(
                onPressed: () => NewrelicMobile.instance
                    .recordBreadcrumb("Button Got Pressed on Screen 3"),
                child: const Text('Record BreadCrumb Event'),
              ),
              ElevatedButton(
                onPressed: () async {
                  var id = await NewrelicMobile.instance
                      .startInteraction("Getting Data from Service");
                  try {
                    var dio = Dio();
                    var response =
                        await dio.get('https://reqres.in/api/users?delay=15');
                    if (kDebugMode) {
                      print(response);
                    }
                    NewrelicMobile.instance.endInteraction(id);
                  } catch (e) {
                    if (kDebugMode) {
                      print(e);
                    }
                  }
                },
                child: const Text('Interaction Example'),
              ),
              ElevatedButton(
                onPressed: () =>
                    Navigator.pushReplacementNamed(context, 'pagefour'),
                child: const Text('Go to Isolate page'),
              ),
            ],
          ),
        ),
      );
}

class Page4Screen extends StatefulWidget {
  /// Creates a [Page2Screen].
  const Page4Screen({Key? key}) : super(key: key);

  @override
  State<Page4Screen> createState() => _Page4ScreenState();
}

class _Page4ScreenState extends State<Page4Screen> {
  Person? person;
  final computeService = ComputeService();
  final spawnService = SpawnService();

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(title: const Text("Page 3")),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                person?.name ?? 'Hello World',
                style: Theme.of(context).textTheme.titleLarge,
              ),
              ElevatedButton(
                onPressed: () async {
                  ReceivePort port = ReceivePort();
                  var errorPort = ReceivePort();
                  errorPort.listen((message) {
                    if (kDebugMode) {
                      print('Error: $message');
                    }
                    NewrelicMobile.instance
                        .recordError(message, StackTrace.current);
                  });
                  await Isolate.spawn(_isolateFunction, port.sendPort,
                      onError: errorPort.sendPort);

                  // computeService.fetchUser().then((value) {
                  //   setState(() {
                  //     person = value;
                  //   });
                  // });
                },
                child: const Text('Isolate Compute Error'),
              ),
              ElevatedButton(
                onPressed: () {
                  spawnService.fetchUser().then((value) {
                    setState(() {
                      person = value;
                    });
                  });
                },
                child: const Text('Isolate Compute Error'),
              ),
              ElevatedButton(
                onPressed: () {
                  NewrelicMobile.instance.shutDown();
                },
                child: const Text('shutDown Agent'),
              )
            ],
          ),
        ),
      );
}

void _isolateFunction(_) {
  throw Exception('Uncaught error in isolate');
}

class ComputeService {
  Future<Person> fetchUser() async {
    String userData = await Api.getUser("Compute");
    return await compute(deserializeJson, userData);
  }

  Person deserializeJson(String data) {
    throw Error();
  }
}

class Person {
  final String name;

  Person(this.name);
}

class Api {
  static Future<String> getUser(String from) =>
      Future.value("{\"name\":\"John Smith ..via $from\"}");
}

class SpawnService {
  Future<Person?> fetchUser() async {
    ReceivePort port = ReceivePort();
    String userData = await Api.getUser("Spawn");
    var isolate = await Isolate.spawn<List<dynamic>>(
        deserializePerson, [port.sendPort, userData]);
    isolate.addErrorListener(port.sendPort);
    return await port.first;
  }

  void deserializePerson(List<dynamic> values) async {
    var dio = Dio();
    var response = await dio.get('https://reqres.in/api/users?delay=15');
    if (kDebugMode) {
      print(response);
    }
    throw Exception("this is isplation error");
  }
}

final HttpLink rickAndMortyHttpLink = HttpLink('https://graphql.anilist.co');
ValueNotifier<GraphQLClient> client = ValueNotifier(
  GraphQLClient(
    link: rickAndMortyHttpLink,
    cache: GraphQLCache(
      store: InMemoryStore(),
    ),
  ),
);

const rickCharacters = '''
 query country(\$code: IN) {
    name
    native
    capital
    emoji
    currency
    languages {
      code
      name
    }
  }
 ''';

class PopPopPop extends StatelessWidget {
  const PopPopPop({super.key});

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
        title: const Text('Here we go...'),
        icon: const Icon(
          Icons.error_outline,
          color: Colors.red,
          size: 18.0,
        ),
        content: const SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text('Something went wrong but we\'re on it.'),
            ],
          ),
        ),
        actions: <Widget>[
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              TextButton(
                onPressed: () {
                  Navigator.pushNamed(context, 'pagetwo',
                      arguments: {'id': ""});
                },
                child: const Text('OK'),
              ),
            ],
          )
        ]);
  }
}
43
likes
0
points
80.3k
downloads

Publisher

verified publishernewrelic.com

Weekly Downloads

Flutter plugin for NewRelic Mobile. This plugin allows you to instrument Flutter apps with help of native New Relic Android and iOS agents.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, go_router, stack_trace

More

Packages that depend on newrelic_mobile

Packages that implement newrelic_mobile