appxiomcoreplugin 1.0.1 copy "appxiomcoreplugin: ^1.0.1" to clipboard
appxiomcoreplugin: ^1.0.1 copied to clipboard

Detect and report bugs in Flutter based mobile apps. Reports issues like memory leaks, crashes, ANR and exceptions. Plugin has low memory and size footprint.

example/lib/main.dart

import 'dart:convert';

import 'package:appxiomcoreplugin/observe.dart';
import 'package:flutter/material.dart';
import 'dart:async';

import 'package:appxiomcoreplugin/appxiomcoreplugin.dart';
import 'package:appxiomcoreplugin/state_custom.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized(); // Must be called first

  Ax.init("<android_app_key>",
      "<android_platform_key>"); //Get appropriate keys from Appxiom dashboard.
  Ax.initIOS(); //Add app_id and platform_id to info.plist. Get appropriate keys from Appxiom dashboard.

  Observe().setUrlPatterns([ //To Make sure URLs with dynamic segments are grouped together as a single ticket.
    '/users/{id}',
    '/posts/{id}'
  ]);
  
  Observe().setMaskedHeaders([ //To mask sensitive headers in both requests and responses from being reported to Appxiom dashboard.
    'X-API-KEY'
  ]);

  // Run the app last
  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Memory Leak Detection Test',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomeScreen(),
    );
  }
}

/// Home screen with navigation to test screens
class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends AxInitialState<HomeScreen> {
  String _apiResult = 'No API call made yet';
  bool _isLoading = false;

  // Goal tracking variables
  int? _currentGoalId;
  String _goalStatus = 'No goal started';
  final List<Map<String, dynamic>> _completedGoals = [];

  // Activity markers variables
  int _markerCount = 5;
  String _markerStatus = 'No markers set yet';

  Future<void> _makeApiCall() async {
    setState(() {
      _isLoading = true;
      _apiResult = 'Loading...';
    });

    try {
      // This will be automatically tracked by Appxiom using HttpInterceptor
      final response = await AxHttpInterceptor.get(
        Uri.parse('<url>'),
        headers: {'Content-Type': 'application/json'},
      );

      if (response.statusCode == 200) {
        json.decode(response.body);
        setState(() {
          _apiResult = 'Success! (Tracked by HttpInterceptor.get)';
        });
      } else {
        setState(() {
          _apiResult =
              'Error: ${response.statusCode} - ${response.reasonPhrase}';
        });
      }
    } catch (e) {
      setState(() {
        _apiResult = 'Exception occurred: $e';
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  Future<void> _makeTrackedApiCall() async {
    setState(() {
      _isLoading = true;
      _apiResult = 'Loading with tracked client...';
    });

    try {
      // Use the tracked client to ensure API tracking
      final client = AxHttpInterceptor.createAxClient();
      final response = await client.get(
        Uri.parse('<url>'),
        headers: {'Content-Type': 'application/json'},
      );
      client.close();

      if (response.statusCode == 200) {
        final data = json.decode(response.body);
        setState(() {
          _apiResult = 'Success with TrackedClient!\n\n'
              'ID: ${data['id']}\n'
              'Title: ${data['title']}\n'
              'Completed: ${data['completed']}\n'
              'User ID: ${data['userId']}';
        });
      } else {
        setState(() {
          _apiResult =
              'Error: ${response.statusCode} - ${response.reasonPhrase}';
        });
      }
    } catch (e) {
      setState(() {
        _apiResult = 'Exception occurred: $e';
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  void _reportTestIssue() {
    //Give a descirption and detailed description for the issue being reported
    Ax.reportIssue("main", 'Payment attempt failed',
        'User attempted to make a payment but it failed due to incorrect production credentials.');
  }

  // POST API call with random JSON
  Future<void> _makePostApiCall() async {
    setState(() {
      _isLoading = true;
      _apiResult = 'Making POST request...';
    });

    try {
      // Generate random JSON data
      final randomData = {'title': 'title', 'body': 'body'};

      final response = await AxHttpInterceptor.post(
        Uri.parse('<url>'),
        headers: {
          'Content-Type': 'application/json; charset=UTF-8',
          'Accept': 'application/json',
        },
        body: json.encode(randomData),
      );

      if (response.statusCode == 201 || response.statusCode == 200) {
        json.decode(response.body);
        setState(() {
          _apiResult = 'POST Success! (Tracked by HttpInterceptor.post)\n\n'
              'Status: ${response.statusCode}';
        });
      } else {
        setState(() {
          _apiResult =
              'POST Error: ${response.statusCode} - ${response.reasonPhrase}';
        });
      }
    } catch (e) {
      setState(() {
        _apiResult = 'POST Exception occurred: $e';
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  // URL Pattern Testing Methods
  Future<void> _testTodosPattern1() async {
    try {
      await AxHttpInterceptor.get(
        Uri.parse('<url>'),
      );
    } catch (e) {
      debugPrint('Error: $e');
    }
  }

  Future<void> _testTodosPattern2() async {
    try {
      await AxHttpInterceptor.get(
        Uri.parse('<url>'),
      );
    } catch (e) {
      debugPrint('Error: $e');
    }
  }

  Future<void> _testUsersPattern() async {
    try {
      await AxHttpInterceptor.get(
        Uri.parse('<url>'),
      );
    } catch (e) {
      debugPrint('Error: $e');
    }
  }

  Future<void> _testCommentsPattern() async {
    try {
      await AxHttpInterceptor.get(
        Uri.parse('<url>'),
      );
    } catch (e) {
      debugPrint('Error: $e');
    }
  }

  // Goal Management Methods
  Future<void> _beginGoal1() async {
    try {
      final goalId = await Ax.beginGoal("main", 'Sign_Up');
      setState(() {
        _currentGoalId = goalId;
        _goalStatus =
            'Goal 1 Active: Complete API Integration Test (ID: $goalId)';
      });
    } catch (e) {
      setState(() {
        _goalStatus = 'Error starting Goal 1: $e';
      });
    }
  }

  Future<void> _beginGoal2() async {
    try {
      final goalId = await Ax.beginGoal("main", 'Payment_Process');
      setState(() {
        _currentGoalId = goalId;
        _goalStatus = 'Goal 2 Active: Test URL Pattern Matching (ID: $goalId)';
      });
    } catch (e) {
      setState(() {
        _goalStatus = 'Error starting Goal 2: $e';
      });
    }
  }

  void _completeCurrentGoal() {
    if (_currentGoalId == null) {
      setState(() {
        _goalStatus = 'No active goal to complete';
      });
      return;
    }

    try {
      Ax.completeGoal("main", _currentGoalId!);

      setState(() {
        _completedGoals.add({
          'id': _currentGoalId,
          'completedAt': DateTime.now().toIso8601String(),
        });
        _goalStatus =
            'Goal completed! ID: $_currentGoalId (Total completed: ${_completedGoals.length})';
        _currentGoalId = null;
      });
    } catch (e) {
      setState(() {
        _goalStatus = 'Error completing goal: $e';
      });
    }
  }

  // Activity Markers Method
  void _setMultipleActivityMarkers() {
    try {
      for (int i = 1; i <= _markerCount; i++) {
        Ax.setActivityMarkerAt("main", "Marker $i");
      }
      setState(() {
        _markerStatus =
            'Set $_markerCount activity markers (1 to $_markerCount)';
      });
    } catch (e) {
      setState(() {
        _markerStatus = 'Error setting markers: $e';
      });
    }
  }

  void _incrementMarkerCount() {
    setState(() {
      _markerCount++;
    });
  }

  void _decrementMarkerCount() {
    if (_markerCount > 1) {
      setState(() {
        _markerCount--;
      });
    }
  }

  // Crash Testing Functions
  void _testCrashReporting() {
    // This will trigger a Flutter crash that should be caught by FlutterError.onError
    throw FlutterError('Null check operator on a null value');
  }

  void _testCustomException() {
    try {
      // Simulate a custom exception
      throw Exception(
          'Unhandled Exception: LateInitializationError: Field _userData@123451234 has not been initialized.');
    } catch (error, stackTrace) {
      // Report the error using Ax.reportError API
      Ax.reportError(error, stackTrace);

      // Show a message to user that the error was reported
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text('Custom exception reported via Ax.reportError API'),
            backgroundColor: Colors.green,
          ),
        );
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    // Call ActivityTrail marker manually since super.build() throws UnimplementedError
    Ax.setActivityMarkerAt(
        runtimeType.toString(), "build ${context.toString()}");

    return Scaffold(
      appBar: AppBar(
        title: const Text('Memory Leak Test App'),
        backgroundColor: Colors.blue,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: SingleChildScrollView(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              const Text(
                'Test Memory Leak Detection',
                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 20),
              const Text(
                'Navigate to different screens to test State lifecycle tracking and memory leak detection.',
                style: TextStyle(fontSize: 16),
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 30),
              _buildNavigationButton(
                context,
                'Simple Screen Test',
                'Test basic AxState lifecycle',
                () => Navigator.push(
                    context,
                    MaterialPageRoute(
                        builder: (_) => const SimpleTestScreen())),
              ),
              const SizedBox(height: 16),
              _buildNavigationButton(
                context,
                'Timer Screen (Potential Leak)',
                'Test screen with Timer that may leak',
                () => Navigator.push(context,
                    MaterialPageRoute(builder: (_) => const TimerTestScreen())),
              ),
              const SizedBox(height: 16),
              _buildNavigationButton(
                context,
                'Stream Screen (Potential Leak)',
                'Test screen with Stream subscription',
                () => Navigator.push(
                    context,
                    MaterialPageRoute(
                        builder: (_) => const StreamTestScreen())),
              ),
              const SizedBox(height: 16),
              _buildNavigationButton(
                context,
                'Animation Screen',
                'Test screen with AnimationController',
                () => Navigator.push(
                    context,
                    MaterialPageRoute(
                        builder: (_) => const AnimationTestScreen())),
              ),
              const SizedBox(height: 16),
              _buildNavigationButton(
                context,
                'Multiple Screens Rapid',
                'Rapidly create/dispose multiple screens',
                () => _navigateRapidScreens(context),
              ),
              const SizedBox(height: 16),
              _buildNavigationButton(
                context,
                'Static Reference Screen',
                'Screen that adds itself to static list',
                () => Navigator.push(
                    context,
                    MaterialPageRoute(
                        builder: (_) => const StaticReferenceScreen())),
              ),
              const SizedBox(height: 30),
              const Divider(),
              const Text(
                'API Testing',
                style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 16),
              Row(
                children: [
                  Expanded(
                    child: ElevatedButton.icon(
                      onPressed: _isLoading ? null : _makeApiCall,
                      icon: _isLoading
                          ? const SizedBox(
                              width: 16,
                              height: 16,
                              child: CircularProgressIndicator(strokeWidth: 2),
                            )
                          : const Icon(Icons.api),
                      label: const Text('Interceptor API'),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.blue,
                        foregroundColor: Colors.white,
                        padding: const EdgeInsets.symmetric(
                            horizontal: 16, vertical: 12),
                      ),
                    ),
                  ),
                  const SizedBox(width: 8),
                  Expanded(
                    child: ElevatedButton.icon(
                      onPressed: _isLoading ? null : _makeTrackedApiCall,
                      icon: _isLoading
                          ? const SizedBox(
                              width: 16,
                              height: 16,
                              child: CircularProgressIndicator(strokeWidth: 2),
                            )
                          : const Icon(Icons.track_changes),
                      label: const Text('Tracked API'),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.green,
                        foregroundColor: Colors.white,
                        padding: const EdgeInsets.symmetric(
                            horizontal: 16, vertical: 12),
                      ),
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 16),
              ElevatedButton.icon(
                onPressed: _isLoading ? null : _makePostApiCall,
                icon: _isLoading
                    ? const SizedBox(
                        width: 16,
                        height: 16,
                        child: CircularProgressIndicator(strokeWidth: 2),
                      )
                    : const Icon(Icons.send),
                label: const Text('POST API with Random JSON'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.purple,
                  foregroundColor: Colors.white,
                  padding:
                      const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
                ),
              ),
              const SizedBox(height: 16),
              ElevatedButton.icon(
                onPressed: _reportTestIssue,
                icon: const Icon(Icons.bug_report),
                label: const Text('Report Test Issue'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.orange,
                  foregroundColor: Colors.white,
                  padding:
                      const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
                ),
              ),
              const SizedBox(height: 24),
              const Divider(),
              const Text(
                'Crash & Exception Testing',
                style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                textAlign: TextAlign.center,
              ),
              const Text(
                'Test crash reporting via FlutterError.onError and custom exceptions via Ax.reportError',
                style: TextStyle(fontSize: 12, color: Colors.grey),
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 16),
              Row(
                children: [
                  Expanded(
                    child: ElevatedButton.icon(
                      onPressed: _testCrashReporting,
                      icon: const Icon(Icons.error_outline),
                      label: const Text('Test Crash'),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.red,
                        foregroundColor: Colors.white,
                        padding: const EdgeInsets.symmetric(
                            horizontal: 16, vertical: 12),
                      ),
                    ),
                  ),
                  const SizedBox(width: 8),
                  Expanded(
                    child: ElevatedButton.icon(
                      onPressed: _testCustomException,
                      icon: const Icon(Icons.warning),
                      label: const Text('Custom Error'),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.deepOrange,
                        foregroundColor: Colors.white,
                        padding: const EdgeInsets.symmetric(
                            horizontal: 16, vertical: 12),
                      ),
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 8),
              const SizedBox(height: 24),
              const Text(
                'URL Pattern Testing',
                style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
              ),
              const Text(
                'Test API calls that match configured patterns',
                style: TextStyle(fontSize: 12, color: Colors.grey),
              ),
              const SizedBox(height: 12),
              ElevatedButton.icon(
                onPressed: _testTodosPattern1,
                icon: const Icon(Icons.filter_1),
                label: const Text('Test /todos/1 (Pattern)'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.purple,
                  foregroundColor: Colors.white,
                ),
              ),
              const SizedBox(height: 8),
              ElevatedButton.icon(
                onPressed: _testTodosPattern2,
                icon: const Icon(Icons.filter_2),
                label: const Text('Test /todos/42 (Pattern)'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.purple,
                  foregroundColor: Colors.white,
                ),
              ),
              const SizedBox(height: 8),
              ElevatedButton.icon(
                onPressed: _testUsersPattern,
                icon: const Icon(Icons.person),
                label: const Text('Test /users/123 (Pattern)'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.indigo,
                  foregroundColor: Colors.white,
                ),
              ),
              const SizedBox(height: 8),
              ElevatedButton.icon(
                onPressed: _testCommentsPattern,
                icon: const Icon(Icons.comment),
                label: const Text('Test /posts/1/comments/5 (Pattern)'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.teal,
                  foregroundColor: Colors.white,
                ),
              ),
              const SizedBox(height: 24),
              const Text(
                'Goal Management',
                style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
              ),
              const Text(
                'Test goal tracking functionality',
                style: TextStyle(fontSize: 12, color: Colors.grey),
              ),
              const SizedBox(height: 12),
              Row(
                children: [
                  Expanded(
                    child: ElevatedButton.icon(
                      onPressed: _beginGoal1,
                      icon: const Icon(Icons.flag),
                      label: const Text('Begin Goal 1'),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.green,
                        foregroundColor: Colors.white,
                      ),
                    ),
                  ),
                  const SizedBox(width: 8),
                  Expanded(
                    child: ElevatedButton.icon(
                      onPressed: _beginGoal2,
                      icon: const Icon(Icons.flag_outlined),
                      label: const Text('Begin Goal 2'),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.blue,
                        foregroundColor: Colors.white,
                      ),
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 8),
              ElevatedButton.icon(
                onPressed: _currentGoalId != null ? _completeCurrentGoal : null,
                icon: const Icon(Icons.check_circle),
                label: Text(_currentGoalId != null
                    ? 'Complete Current Goal'
                    : 'No Active Goal'),
                style: ElevatedButton.styleFrom(
                  backgroundColor:
                      _currentGoalId != null ? Colors.orange : Colors.grey,
                  foregroundColor: Colors.white,
                  minimumSize: const Size(double.infinity, 48),
                ),
              ),
              const SizedBox(height: 8),
              Container(
                width: double.infinity,
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: Colors.blue.shade50,
                  border: Border.all(color: Colors.blue.shade200),
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text(
                      'Goal Status:',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                    const SizedBox(height: 4),
                    Text(_goalStatus),
                    if (_completedGoals.isNotEmpty) ...[
                      const SizedBox(height: 8),
                      Text(
                        'Completed Goals: ${_completedGoals.length}',
                        style: TextStyle(
                          color: Colors.green.shade700,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ],
                  ],
                ),
              ),
              const SizedBox(height: 24),
              const Text(
                'Activity Markers',
                style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
              ),
              const Text(
                'Set multiple activity markers at once',
                style: TextStyle(fontSize: 12, color: Colors.grey),
              ),
              const SizedBox(height: 12),
              Row(
                children: [
                  const Text('Count:',
                      style: TextStyle(fontWeight: FontWeight.bold)),
                  const SizedBox(width: 8),
                  IconButton(
                    onPressed: _decrementMarkerCount,
                    icon: const Icon(Icons.remove),
                    style: IconButton.styleFrom(
                      backgroundColor: Colors.red.shade100,
                    ),
                  ),
                  Container(
                    margin: const EdgeInsets.symmetric(horizontal: 8),
                    padding:
                        const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                    decoration: BoxDecoration(
                      border: Border.all(color: Colors.grey),
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: Text(
                      '$_markerCount',
                      style: const TextStyle(
                          fontSize: 18, fontWeight: FontWeight.bold),
                    ),
                  ),
                  IconButton(
                    onPressed: _incrementMarkerCount,
                    icon: const Icon(Icons.add),
                    style: IconButton.styleFrom(
                      backgroundColor: Colors.green.shade100,
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 8),
              ElevatedButton.icon(
                onPressed: _setMultipleActivityMarkers,
                icon: const Icon(Icons.timeline),
                label: Text('Set $_markerCount Activity Markers'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.deepPurple,
                  foregroundColor: Colors.white,
                  minimumSize: const Size(double.infinity, 48),
                ),
              ),
              const SizedBox(height: 8),
              Container(
                width: double.infinity,
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: Colors.purple.shade50,
                  border: Border.all(color: Colors.purple.shade200),
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text(
                      'Marker Status:',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                    const SizedBox(height: 4),
                    Text(_markerStatus),
                  ],
                ),
              ),
              const SizedBox(height: 16),
              Container(
                padding: const EdgeInsets.all(16),
                decoration: BoxDecoration(
                  border: Border.all(color: Colors.grey),
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text(
                      'API Response:',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      _apiResult,
                      style: const TextStyle(fontSize: 14),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildNavigationButton(BuildContext context, String title,
      String subtitle, VoidCallback onPressed) {
    return ElevatedButton(
      style: ElevatedButton.styleFrom(
        padding: const EdgeInsets.all(16),
      ),
      onPressed: onPressed,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            title,
            style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 4),
          Text(
            subtitle,
            style: const TextStyle(fontSize: 12),
          ),
        ],
      ),
    );
  }

  void _navigateRapidScreens(BuildContext context) async {
    for (int i = 0; i < 5; i++) {
      if (!mounted) return;
      Navigator.push(
          context,
          MaterialPageRoute(
              builder: (_) => RapidTestScreen(screenNumber: i + 1)));
      await Future.delayed(const Duration(milliseconds: 500));
      if (!mounted) return;
      Navigator.pop(context);
      await Future.delayed(const Duration(milliseconds: 200));
    }
  }
}

/// Simple test screen using AxState
class SimpleTestScreen extends StatefulWidget {
  const SimpleTestScreen({super.key});

  @override
  State<SimpleTestScreen> createState() => _SimpleTestScreenState();
}

class _SimpleTestScreenState extends AxState<SimpleTestScreen> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    Ax.setActivityMarkerAt(
        runtimeType.toString(), "build ${context.toString()}");

    return Scaffold(
      appBar: AppBar(
        title: const Text('Simple Test Screen'),
        backgroundColor: Colors.green,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'This screen uses AxState and should be properly disposed.',
              textAlign: TextAlign.center,
              style: TextStyle(fontSize: 16),
            ),
            const SizedBox(height: 20),
            Text(
              'Counter: $_counter',
              style: const TextStyle(fontSize: 24),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _counter++;
                });
              },
              child: const Text('Increment'),
            ),
          ],
        ),
      ),
    );
  }
}

/// Screen with Timer that might leak if not properly disposed
class TimerTestScreen extends StatefulWidget {
  const TimerTestScreen({super.key});

  @override
  State<TimerTestScreen> createState() => _TimerTestScreenState();
}

class _TimerTestScreenState extends AxState<TimerTestScreen> {
  Timer? _timer;
  int _seconds = 0;

  @override
  void initState() {
    super.initState();
    // Start a timer - potential memory leak if not cancelled
    _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      setState(() {
        _seconds++;
      });
    });
  }

  @override
  void dispose() {
    _timer?.cancel(); // Properly cancel timer
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    Ax.setActivityMarkerAt(
        runtimeType.toString(), "build ${context.toString()}");

    return Scaffold(
      appBar: AppBar(
        title: const Text('Timer Test Screen'),
        backgroundColor: Colors.orange,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'This screen has a Timer.',
              style: TextStyle(fontSize: 18),
            ),
            const Text(
              'Timer is properly cancelled in dispose().',
              style: TextStyle(fontSize: 14),
            ),
            const SizedBox(height: 20),
            Text(
              'Seconds: $_seconds',
              style: const TextStyle(fontSize: 24),
            ),
            const SizedBox(height: 20),
            const Text(
              'Navigate back to test disposal',
              style: TextStyle(fontSize: 14, color: Colors.grey),
            ),
          ],
        ),
      ),
    );
  }
}

/// Screen with Stream subscription
class StreamTestScreen extends StatefulWidget {
  const StreamTestScreen({super.key});

  @override
  State<StreamTestScreen> createState() => _StreamTestScreenState();
}

class _StreamTestScreenState extends AxState<StreamTestScreen> {
  StreamSubscription<int>? _subscription;
  int _value = 0;

  @override
  void initState() {
    super.initState();
    // Create a stream subscription
    _subscription = Stream.periodic(const Duration(milliseconds: 500), (i) => i)
        .listen((value) {
      setState(() {
        _value = value;
      });
    });
  }

  @override
  void dispose() {
    _subscription?.cancel(); // Properly cancel subscription
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    Ax.setActivityMarkerAt(
        runtimeType.toString(), "build ${context.toString()}");

    return Scaffold(
      appBar: AppBar(
        title: const Text('Stream Test Screen'),
        backgroundColor: Colors.purple,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'This screen has a Stream subscription.',
              style: TextStyle(fontSize: 18),
            ),
            const Text(
              'Subscription is properly cancelled in dispose().',
              style: TextStyle(fontSize: 14),
            ),
            const SizedBox(height: 20),
            Text(
              'Stream Value: $_value',
              style: const TextStyle(fontSize: 24),
            ),
          ],
        ),
      ),
    );
  }
}

/// Screen with AnimationController
class AnimationTestScreen extends StatefulWidget {
  const AnimationTestScreen({super.key});

  @override
  State<AnimationTestScreen> createState() => _AnimationTestScreenState();
}

class _AnimationTestScreenState extends AxState<AnimationTestScreen>
    with TickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );
    _animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
    _controller.repeat(reverse: true);
  }

  @override
  void dispose() {
    _controller.dispose(); // Properly dispose animation controller
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    Ax.setActivityMarkerAt(
        runtimeType.toString(), "build ${context.toString()}");

    return Scaffold(
      appBar: AppBar(
        title: const Text('Animation Test Screen'),
        backgroundColor: Colors.teal,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'This screen has an AnimationController.',
              style: TextStyle(fontSize: 18),
            ),
            const Text(
              'Controller is properly disposed.',
              style: TextStyle(fontSize: 14),
            ),
            const SizedBox(height: 20),
            AnimatedBuilder(
              animation: _animation,
              builder: (context, child) {
                return Opacity(
                  opacity: _animation.value,
                  child: Container(
                    width: 100,
                    height: 100,
                    decoration: const BoxDecoration(
                      color: Colors.teal,
                      shape: BoxShape.circle,
                    ),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

/// Screen for rapid navigation testing
class RapidTestScreen extends StatefulWidget {
  final int screenNumber;

  const RapidTestScreen({super.key, required this.screenNumber});

  @override
  State<RapidTestScreen> createState() => _RapidTestScreenState();
}

class _RapidTestScreenState extends AxState<RapidTestScreen> {
  @override
  Widget build(BuildContext context) {
    Ax.setActivityMarkerAt(
        runtimeType.toString(), "build ${context.toString()}");

    return Scaffold(
      appBar: AppBar(
        title: Text('Rapid Test ${widget.screenNumber}'),
        backgroundColor: Colors.red,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Rapid Screen #${widget.screenNumber}',
              style: const TextStyle(fontSize: 24),
            ),
            const SizedBox(height: 20),
            const Text(
              'This screen will be disposed quickly',
              style: TextStyle(fontSize: 16),
            ),
          ],
        ),
      ),
    );
  }
}

/// Screen that intentionally creates a potential memory leak by adding itself to a static list
class StaticReferenceScreen extends StatefulWidget {
  const StaticReferenceScreen({super.key});

  @override
  State<StaticReferenceScreen> createState() => _StaticReferenceScreenState();
}

// Static list that holds references (potential memory leak)
class _GlobalStateHolder {
  static final List<State> _states = [];

  static void addState(State state) {
    _states.add(state);
  }

  static void removeState(State state) {
    _states.remove(state);
  }

  static int get stateCount => _states.length;
}

class _StaticReferenceScreenState extends AxState<StaticReferenceScreen> {
  bool _isAddedToGlobalList = false;

  void _toggleGlobalReference() {
    setState(() {
      if (_isAddedToGlobalList) {
        _GlobalStateHolder.removeState(this);
        _isAddedToGlobalList = false;
      } else {
        _GlobalStateHolder.addState(this);
        _isAddedToGlobalList = true;
      }
    });
  }

  @override
  void dispose() {
    // Uncomment the line below to properly clean up and avoid memory leaks
    // if (_isAddedToGlobalList) _GlobalStateHolder.removeState(this);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    Ax.setActivityMarkerAt(
        runtimeType.toString(), "build ${context.toString()}");

    return Scaffold(
      appBar: AppBar(
        title: const Text('Static Reference Test'),
        backgroundColor: Colors.red,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'Memory Leak Simulation',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 20),
            Text(
              'Global state count: ${_GlobalStateHolder.stateCount}',
              style: const TextStyle(fontSize: 16),
            ),
            const SizedBox(height: 20),
            Text(
              _isAddedToGlobalList
                  ? 'This State IS in global list (potential leak if not removed in dispose)'
                  : 'This State is NOT in global list',
              style: TextStyle(
                fontSize: 16,
                color: _isAddedToGlobalList ? Colors.red : Colors.green,
                fontWeight: FontWeight.bold,
              ),
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 30),
            ElevatedButton(
              onPressed: _toggleGlobalReference,
              style: ElevatedButton.styleFrom(
                backgroundColor:
                    _isAddedToGlobalList ? Colors.red : Colors.green,
              ),
              child: Text(
                _isAddedToGlobalList
                    ? 'Remove from Global List'
                    : 'Add to Global List (Create Leak)',
              ),
            ),
            const SizedBox(height: 20),
            const Text(
              'Navigate back after adding to global list to test memory leak detection.',
              style: TextStyle(fontSize: 14, color: Colors.grey),
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 10),
            const Text(
              'Check logcat for memory leak reports!',
              style: TextStyle(
                  fontSize: 14,
                  color: Colors.orange,
                  fontWeight: FontWeight.bold),
              textAlign: TextAlign.center,
            ),
          ],
        ),
      ),
    );
  }
}
1
likes
0
points
303
downloads

Publisher

unverified uploader

Weekly Downloads

Detect and report bugs in Flutter based mobile apps. Reports issues like memory leaks, crashes, ANR and exceptions. Plugin has low memory and size footprint.

Homepage

Documentation

Documentation

License

unknown (license)

Dependencies

flutter, http, plugin_platform_interface, stack_trace, uuid

More

Packages that depend on appxiomcoreplugin

Packages that implement appxiomcoreplugin