New Relic Flutter統合設定 - Dart・ネイティブ統合監視の完全ガイド

Flutterアプリケーションは、Dartランタイムとプラットフォーム固有のネイティブコードが密接に連携して動作します。New Relic Flutter Agentは、この複雑な構造を統合的に監視し、Flutterアプリケーションの包括的なパフォーマンス分析を提供します。本ガイドでは、Flutter開発環境での完全な導入手順から高度な設定まで、実践的な実装方法を詳しく解説します。

Flutter Agent概要と監視機能

New Relic Flutter Agentは、Flutter 2.0以上をサポートし、Dartコードの実行パフォーマンス、ネイティブプラットフォームとの相互作用、ユーザーインターフェースの応答性を統合的に監視できる包括的なSDKです。Flutter Engine、Dart VM、プラットフォーム固有のコードすべてを対象とした詳細な分析機能を提供します。

Flutter特有の監視項目

Flutterアプリケーションの独特な特性に対応した以下の監視機能を提供します。

Dart VMパフォーマンスでは、Dartコードの実行時間、ガベージコレクション頻度、メモリ使用量を詳細に追跡します。JIT(Just-In-Time)コンパイルとAOT(Ahead-Of-Time)コンパイルの両方に対応し、実行環境に応じた最適化指針を提供します。

Widget Tree監視により、ウィジェットのビルド時間、レンダリングパフォーマンス、レイアウト計算時間を測定します。複雑なウィジェット階層でのパフォーマンスボトルネックを特定し、UIの応答性向上に必要な情報を提供します。

Platform Channel通信監視では、DartコードとiOS・Androidネイティブコード間のメソッドチャネル通信を追跡し、データ転送効率と通信遅延を分析します。プラットフォーム固有機能の呼び出しパフォーマンスを最適化できます。

フレームレート分析により、60FPSまたは120FPSでの描画パフォーマンスを継続的に監視し、スムーズなアニメーションとインタラクションの実現を支援します。UI jankやフレームドロップの発生箇所を特定できます。

アプリライフサイクル管理では、Flutter特有のアプリケーション状態遷移、ホットリロード、ホットリスタートの影響を追跡し、開発効率とユーザー体験の両方を最適化できます。

事前準備とシステム要件

Flutter Agent導入前の環境確認を行います。

システム要件

  • Flutter 2.0以上(Flutter 3.0以上を推奨)
  • Dart 2.17以上
  • iOS: Xcode 12以上、iOS 11以上
  • Android: Android Studio 4.0以上、Android API 21以上
  • New Relicアカウントとアプリケーショントークン

開発環境の確認

既存のFlutterプロジェクトがあることを前提とし、以下のコマンドで環境を確認してください。

bash
flutter doctor -v

Flutter SDK、プラットフォーム固有の開発環境、エミュレータまたは実機の接続状況が適切に設定されていることを確認します。

アプリケーショントークンの取得

New Relicダッシュボードで「Add Data」→「Mobile」→「Flutter」を選択し、新しいアプリケーションを作成します。iOSとAndroid用に個別のアプリケーショントークンが生成される場合があります。両方のトークンを安全に保管してください。

pub.devパッケージの導入

Flutter Agent用パッケージをプロジェクトに追加します。

pubspec.yamlの設定

プロジェクトルートのpubspec.yamlファイルにdependenciesを追加します。

yaml
name: your_flutter_app
description: A new Flutter project.

version: 1.0.0+1

environment:
  sdk: '>=2.17.0 <4.0.0'
  flutter: ">=2.0.0"

dependencies:
  flutter:
    sdk: flutter
  newrelic_mobile: ^1.0.0
  # 他の依存関係

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0

flutter:
  uses-material-design: true

パッケージのインストール

以下のコマンドを実行してパッケージをインストールします。

bash
flutter pub get

プラットフォーム固有の設定

iOS・Android それぞれで必要な設定を実装します。

iOS設定

ios/Runner/AppDelegate.swiftファイルを編集し、New Relic iOS SDKを統合します。

swift
import UIKit
import Flutter
import NewRelic

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    
    // New Relic初期化
    NewRelic.start(withApplicationToken: "YOUR_IOS_APPLICATION_TOKEN")
    
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

ios/Runner.xcodeproj/project.pbxprojにNew Relic iOS SDKを追加するため、Xcodeでプロジェクトを開き、「File」→「Add Package Dependencies」で以下のURLを追加します。

https://github.com/newrelic/newrelic-ios-agent-spm

Android設定

android/app/src/main/kotlin/.../MainActivity.ktファイルを編集します。

kotlin
package com.example.your_flutter_app

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
import com.newrelic.agent.android.NewRelic

class MainActivity: FlutterActivity() {
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)
        
        // New Relic初期化
        NewRelic.withApplicationToken("YOUR_ANDROID_APPLICATION_TOKEN")
               .start(this.applicationContext)
    }
}

android/app/build.gradleファイルにNew Relic Android Agent依存関係を追加します。

gradle
android {
    compileSdkVersion 34
    
    defaultConfig {
        applicationId "com.example.your_flutter_app"
        minSdkVersion 21
        targetSdkVersion 34
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }
}

dependencies {
    implementation 'com.newrelic.agent.android:android-agent:7.0.0'
    // 既存の依存関係
}

プロジェクトレベルのandroid/build.gradleにもプラグインを追加します。

gradle
buildscript {
    dependencies {
        classpath 'com.newrelic.agent.android:agent-gradle-plugin:7.0.0'
        // 既存の依存関係
    }
}

Dart実装

Flutterアプリケーション内でのAgent使用方法を実装します。

基本的な初期化

lib/main.dartファイルでAgentを初期化します。

dart
import 'package:flutter/material.dart';
import 'package:newrelic_mobile/newrelic_mobile.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // New Relic初期化
  NewrelicMobile.instance.start(
    'YOUR_APPLICATION_TOKEN',
    (config) {
      config
        ..loggingEnabled = true
        ..logLevel = LogLevel.info
        ..crashReportingEnabled = true;
    }
  );
  
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter New Relic Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

カスタムメトリクスとイベントの実装

Flutter特有の監視項目を追加します。

dart
import 'package:newrelic_mobile/newrelic_mobile.dart';

class AnalyticsService {
  static final NewrelicMobile _newRelic = NewrelicMobile.instance;
  
  // ユーザー属性の設定
  static Future<void> setUserAttributes(String userId, String userType) async {
    await _newRelic.setUserId(userId);
    await _newRelic.setAttribute('userType', userType);
    await _newRelic.setAttribute('flutterVersion', '3.13.0');
    await _newRelic.setAttribute('dartVersion', '3.1.0');
  }
  
  // カスタムイベントの記録
  static Future<void> recordPurchaseEvent({
    required String itemId,
    required double price,
    required String currency,
  }) async {
    await _newRelic.recordCustomEvent('PurchaseCompleted', {
      'itemId': itemId,
      'price': price,
      'currency': currency,
      'platform': 'flutter',
      'timestamp': DateTime.now().millisecondsSinceEpoch,
    });
  }
  
  // カスタムメトリクスの記録
  static Future<void> recordMetric(String name, double value) async {
    await _newRelic.recordMetric(name, value);
  }
  
  // ウィジェットビルド時間の測定
  static Future<void> recordWidgetBuildTime(String widgetName, Duration buildTime) async {
    await _newRelic.recordMetric(
      'Custom/WidgetBuildTime/$widgetName',
      buildTime.inMilliseconds.toDouble()
    );
  }
}

ナビゲーション監視の実装

Flutter Routerやnamed routesとの統合により、画面遷移の監視を実現します。

dart
import 'package:flutter/material.dart';
import 'package:newrelic_mobile/newrelic_mobile.dart';

class NavigationObserver extends RouteObserver<PageRoute<dynamic>> {
  @override
  void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
    super.didPush(route, previousRoute);
    _recordRouteChange(route.settings.name, 'push');
  }
  
  @override
  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
    super.didPop(route, previousRoute);
    if (previousRoute != null) {
      _recordRouteChange(previousRoute.settings.name, 'pop');
    }
  }
  
  @override
  void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {
    super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
    if (newRoute != null) {
      _recordRouteChange(newRoute.settings.name, 'replace');
    }
  }
  
  void _recordRouteChange(String? routeName, String action) {
    if (routeName != null) {
      NewrelicMobile.instance.recordCustomEvent('ScreenView', {
        'screenName': routeName,
        'action': action,
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      });
    }
  }
}

// MaterialAppでの使用
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorObservers: [NavigationObserver()],
      // 他の設定
    );
  }
}

エラーハンドリングと監視

Flutter特有のエラー処理と監視機能を実装します。

Dartエラーの包括的追跡

dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:newrelic_mobile/newrelic_mobile.dart';

void main() {
  // エラーハンドリングの設定
  runZonedGuarded(() async {
    WidgetsFlutterBinding.ensureInitialized();
    
    // New Relic初期化
    await NewrelicMobile.instance.start('YOUR_APPLICATION_TOKEN');
    
    // Flutter frameworkエラーの捕捉
    FlutterError.onError = (FlutterErrorDetails details) {
      NewrelicMobile.instance.recordError(
        details.exception,
        details.stack,
        attributes: {
          'errorType': 'FlutterError',
          'library': details.library ?? 'unknown',
          'context': details.context.toString(),
        }
      );
    };
    
    runApp(MyApp());
  }, (error, stackTrace) {
    // Zone内の未処理エラーの捕捉
    NewrelicMobile.instance.recordError(
      error,
      stackTrace,
      attributes: {
        'errorType': 'ZonedGuardedError',
        'isFatal': true,
      }
    );
  });
}

// カスタムエラー境界
class ErrorBoundary extends StatefulWidget {
  final Widget child;
  
  const ErrorBoundary({Key? key, required this.child}) : super(key: key);
  
  @override
  _ErrorBoundaryState createState() => _ErrorBoundaryState();
}

class _ErrorBoundaryState extends State<ErrorBoundary> {
  bool hasError = false;
  
  @override
  Widget build(BuildContext context) {
    if (hasError) {
      return Scaffold(
        body: Center(
          child: Text('Something went wrong'),
        ),
      );
    }
    
    return widget.child;
  }
  
  @override
  void initState() {
    super.initState();
    
    // エラー監視の設定
    FlutterError.onError = (FlutterErrorDetails details) {
      setState(() {
        hasError = true;
      });
      
      NewrelicMobile.instance.recordError(
        details.exception,
        details.stack,
        attributes: {
          'component': 'ErrorBoundary',
          'widgetTree': details.toString(),
        }
      );
    };
  }
}

HTTPクライアント監視

dart
import 'package:http/http.dart' as http;
import 'package:newrelic_mobile/newrelic_mobile.dart';

class MonitoredHttpClient {
  static final http.Client _client = http.Client();
  
  static Future<http.Response> get(Uri url, {Map<String, String>? headers}) async {
    return _monitorRequest(() => _client.get(url, headers: headers), 'GET', url);
  }
  
  static Future<http.Response> post(
    Uri url, {
    Map<String, String>? headers,
    Object? body,
  }) async {
    return _monitorRequest(
      () => _client.post(url, headers: headers, body: body),
      'POST',
      url
    );
  }
  
  static Future<http.Response> _monitorRequest(
    Future<http.Response> Function() request,
    String method,
    Uri url,
  ) async {
    final startTime = DateTime.now();
    
    try {
      final response = await request();
      final endTime = DateTime.now();
      final duration = endTime.difference(startTime);
      
      // 成功時のメトリクス記録
      await NewrelicMobile.instance.recordMetric(
        'Custom/HttpRequest/Success',
        duration.inMilliseconds.toDouble()
      );
      
      await NewrelicMobile.instance.recordCustomEvent('HttpRequest', {
        'method': method,
        'url': url.toString(),
        'statusCode': response.statusCode,
        'duration': duration.inMilliseconds,
        'success': true,
      });
      
      return response;
    } catch (error, stackTrace) {
      final endTime = DateTime.now();
      final duration = endTime.difference(startTime);
      
      // エラー時のメトリクス記録
      await NewrelicMobile.instance.recordMetric(
        'Custom/HttpRequest/Error',
        duration.inMilliseconds.toDouble()
      );
      
      await NewrelicMobile.instance.recordError(
        error,
        stackTrace,
        attributes: {
          'method': method,
          'url': url.toString(),
          'requestType': 'http',
        }
      );
      
      rethrow;
    }
  }
}

パフォーマンス監視の実装

Flutter特有のパフォーマンス課題に対応した監視機能を実装します。

ウィジェットビルド時間の測定

dart
import 'package:flutter/material.dart';
import 'package:newrelic_mobile/newrelic_mobile.dart';

class PerformanceMonitoredWidget extends StatefulWidget {
  final Widget child;
  final String widgetName;
  
  const PerformanceMonitoredWidget({
    Key? key,
    required this.child,
    required this.widgetName,
  }) : super(key: key);
  
  @override
  _PerformanceMonitoredWidgetState createState() => _PerformanceMonitoredWidgetState();
}

class _PerformanceMonitoredWidgetState extends State<PerformanceMonitoredWidget> {
  @override
  Widget build(BuildContext context) {
    final startTime = DateTime.now();
    
    return NotificationListener(
      onNotification: (notification) {
        if (notification is! Notification) return false;
        
        WidgetsBinding.instance.addPostFrameCallback((_) {
          final endTime = DateTime.now();
          final buildTime = endTime.difference(startTime);
          
          NewrelicMobile.instance.recordMetric(
            'Custom/WidgetBuild/${widget.widgetName}',
            buildTime.inMicroseconds.toDouble()
          );
        });
        
        return false;
      },
      child: widget.child,
    );
  }
}

// 使用例
class MyExpensiveWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return PerformanceMonitoredWidget(
      widgetName: 'MyExpensiveWidget',
      child: Column(
        children: [
          // 重い処理を含むウィジェット
        ],
      ),
    );
  }
}

フレームレート監視

dart
import 'package:flutter/scheduler.dart';
import 'package:newrelic_mobile/newrelic_mobile.dart';

class FrameRateMonitor {
  static FrameRateMonitor? _instance;
  static FrameRateMonitor get instance => _instance ??= FrameRateMonitor._();
  
  FrameRateMonitor._();
  
  final List<Duration> _frameTimes = [];
  late Ticker _ticker;
  
  void startMonitoring() {
    _ticker = Ticker((elapsed) {
      _frameTimes.add(elapsed);
      
      // 直近100フレームの平均FPSを計算
      if (_frameTimes.length >= 100) {
        _calculateAndReportFPS();
        _frameTimes.clear();
      }
    });
    
    _ticker.start();
  }
  
  void stopMonitoring() {
    _ticker.dispose();
  }
  
  void _calculateAndReportFPS() {
    if (_frameTimes.length < 2) return;
    
    final totalDuration = _frameTimes.last - _frameTimes.first;
    final fps = (_frameTimes.length / totalDuration.inSeconds);
    
    NewrelicMobile.instance.recordMetric('Custom/FrameRate', fps);
    
    // 60FPS未満の場合はイベントとして記録
    if (fps < 55) {
      NewrelicMobile.instance.recordCustomEvent('LowFrameRate', {
        'fps': fps,
        'frameCount': _frameTimes.length,
        'duration': totalDuration.inMilliseconds,
      });
    }
  }
}

トラブルシューティング

Flutter環境でよく発生する問題と解決方法を説明します。

プラットフォーム固有の初期化問題

iOSとAndroidで異なるアプリケーショントークンを使用する場合は、プラットフォーム別の設定を確認してください。

Hot Reloadとの相互作用

Hot Reload時にNew Relicの状態がリセットされる場合があります。開発中は適切な初期化コードの配置を確認してください。

メモリリークの監視

Flutter特有のウィジェット生成サイクルがメモリ使用量に与える影響を定期的に監視し、必要に応じてガベージコレクションの動作を調整してください。

まとめ

New Relic Flutter Agentの適切な導入により、Flutterアプリケーションの包括的なパフォーマンス監視とユーザー体験の最適化が実現できます。Dartランタイムとネイティブプラットフォームの統合監視により、Flutter特有のパフォーマンス課題を効果的に解決できます。

継続的な監視データの活用により、アプリケーションの品質向上とユーザー満足度の最大化を実現してください。開発効率とユーザー体験の両方を向上させる強力なツールとして活用していきましょう。


関連記事: New Relicモバイル監視概要関連記事: New Relic iOS SDK設定ガイド