route_lifecycle_detector 1.1.0 copy "route_lifecycle_detector: ^1.1.0" to clipboard
route_lifecycle_detector: ^1.1.0 copied to clipboard

A lightweight library for detecting Navigator/ModalRoute/BuildContext lifecycles.

A lightweight library for detecting Navigator/ModalRoute/BuildContext lifecycles. It solves dialog multiple display issues caused by multiple asynchronous processes and enables UI display at appropriate timing.

Problem Statement #

Typical problems that occur when multiple asynchronous processes are executed:

  1. Two heavy processes run in parallel on Screen A
  2. Process 1 completes and Screen B opens
  3. Process 2 completes and a dialog is displayed on top of Screen B

Using this library, you can automatically delay dialog display until Screen B closes and returns to Screen A.

Features #

  • Prevent Multiple Dialog Display: Delay UI display while Route is inactive
  • Lifecycle State Detection: Real-time detection of Route's current state (active/inactive/hidden/destroyed)
  • Stream-based Monitoring: Monitor Route lifecycle changes via Stream
  • Lightweight Design: Minimal dependencies and performance overhead
  • Async Process Waiting: Wait for Route to reach appropriate state

Getting started #

Add the library to your pubspec.yaml:

dependencies:
  route_lifecycle_detector: ^1.0.0

Add RouteLifecycleDetector observer to MaterialApp:

import 'package:route_lifecycle_detector/route_lifecycle_detector.dart';

MaterialApp(
  navigatorObservers: [
    RouteLifecycleDetector.navigatorObserver,
  ],
)

Usage #

Problem Example: Multiple Dialog Display #

// Bad example: Unintended multiple dialog display due to multiple async processes
class BadExamplePage extends StatefulWidget {
  @override
  _BadExamplePageState createState() => _BadExamplePageState();
}

class _BadExamplePageState extends State<BadExamplePage> {
  @override
  void initState() {
    super.initState();
    
    // Heavy process 1: Open Screen B after 2 seconds
    Future.delayed(Duration(seconds: 2), () {
      Navigator.push(context, MaterialPageRoute(builder: (_) => PageB()));
    });
    
    // Heavy process 2: Open dialog after 3 seconds (Problem occurs!)
    Future.delayed(Duration(seconds: 3), () {
      showDialog(
        context: context,
        builder: (_) => AlertDialog(title: Text('Task 2 Complete')),
      ); // Dialog appears on top of Screen B
    });
  }
  
  @override
  Widget build(BuildContext context) => Scaffold(/*...*/);
}

Solution: Lifecycle-aware Processing #

import 'package:route_lifecycle_detector/route_lifecycle_detector.dart';

class GoodExamplePage extends StatefulWidget {
  @override
  _GoodExamplePageState createState() => _GoodExamplePageState();
}

class _GoodExamplePageState extends State<GoodExamplePage> {
  @override
  void initState() {
    super.initState();
    
    // Heavy process 1: Open Screen B after 2 seconds
    Future.delayed(Duration(seconds: 2), () {
      Navigator.push(context, MaterialPageRoute(builder: (_) => PageB()));
    });
    
    // Heavy process 2: Open dialog after 3 seconds (with lifecycle consideration)
    Future.delayed(Duration(seconds: 3), () async {
      // Wait until Route becomes active
      final lifecycle = await RouteLifecycleDetector.waitResumeOrDestroy(context);
      
      if (lifecycle == RouteLifecycle.active && mounted) {
        // Display dialog after screen returns to foreground
        showDialog(
          context: context,
          builder: (_) => AlertDialog(title: Text('Task 2 Complete')),
        );
      }
    });
  }
  
  @override
  Widget build(BuildContext context) => Scaffold(/*...*/);
}

Getting Current Lifecycle State #

// Check current lifecycle state before displaying dialog
void showDialogSafely(BuildContext context) {
  final lifecycle = RouteLifecycle.of(context);
  
  if (lifecycle == RouteLifecycle.active) {
    // Display dialog only when Route is at foreground
    showDialog(
      context: context,
      builder: (_) => AlertDialog(title: Text('Safely Displayed')),
    );
  } else {
    // Delay display when Route is inactive
    print('Route is inactive, delaying dialog display');
  }
}

Stream-based Monitoring #

// Monitor lifecycle changes to control dialog display
class SmartDialogController {
  StreamSubscription? _subscription;
  final List<VoidCallback> _pendingDialogs = [];
  
  void startListening(BuildContext context) {
    _subscription = RouteLifecycleDetector.streamOf(context).listen((lifecycle) {
      if (lifecycle == RouteLifecycle.active && _pendingDialogs.isNotEmpty) {
        // Display pending dialogs when Route returns to foreground
        final dialogs = List<VoidCallback>.from(_pendingDialogs);
        _pendingDialogs.clear();
        
        for (final showDialog in dialogs) {
          showDialog();
        }
      }
    });
  }
  
  void showDialogWhenActive(BuildContext context, WidgetBuilder builder) {
    if (RouteLifecycle.of(context) == RouteLifecycle.active) {
      // Display immediately
      showDialog(context: context, builder: builder);
    } else {
      // Hold for later display
      _pendingDialogs.add(() => showDialog(context: context, builder: builder));
    }
  }
  
  void dispose() {
    _subscription?.cancel();
    _pendingDialogs.clear();
  }
}

Waiting for Route Resume #

// Wait until Route resumes or is destroyed
final result = await RouteLifecycleDetector.waitResumeOrDestroy(context);

if (result == RouteLifecycle.active) {
  // Route has resumed
  print('Route has resumed');
} else if (result == RouteLifecycle.destroyed) {
  // Route has been destroyed
  print('Route has been destroyed');
}

Practical Usage Example #

Example of stopping processing while dialog is open and resuming when dialog closes:

class MyPage extends StatefulWidget {
  @override
  _MyPageState createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  StreamSubscription? _subscription;
  
  @override
  void initState() {
    super.initState();
    _startProcessing();
  }
  
  void _startProcessing() {
    _subscription = RouteLifecycleDetector.streamOf(context).listen((lifecycle) {
      if (lifecycle == RouteLifecycle.active) {
        // Execute processing only when Route is at foreground
        _performBackgroundTask();
      }
    });
  }
  
  void _performBackgroundTask() {
    // Execute background task
    print('Task is running...');
  }
  
  @override
  void dispose() {
    _subscription?.cancel();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('My Page')),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            // Display dialog
            await showDialog(
              context: context,
              builder: (_) => AlertDialog(
                content: Text('Dialog'),
                actions: [
                  TextButton(
                    onPressed: () => Navigator.of(context).pop(),
                    child: Text('Close'),
                  ),
                ],
              ),
            );
            // Processing automatically resumes after dialog is closed
          },
          child: Text('Open Dialog'),
        ),
      ),
    );
  }
}

Lifecycle States #

State Description Dialog Display
active App is in foreground and Route is at top of stack 🟢 Safe to display
inactive App is in foreground but Route is not at top of stack 🔴 Should delay display
hidden App is in background state 🔴 Should delay display
building Widget is being built and Route is not yet created 🔴 Cannot display
destroyed Route has been destroyed 🔴 Cannot display

Additional information #

This library combines Flutter's NavigatorObserver with flutter_fgbg to accurately track Route lifecycles.

Key Benefits:

  • Prevent unintended process execution due to dialogs and screen transitions
  • Control considering app foreground/background state
  • Stream-based reactive programming support

Contributing: Bug reports and feature requests are welcome at GitHub Issues.

1
likes
130
points
8
downloads

Documentation

API reference

Publisher

verified publishereaglesakura.com

Weekly Downloads

A lightweight library for detecting Navigator/ModalRoute/BuildContext lifecycles.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, flutter_fgbg, freezed_annotation, meta, rxdart

More

Packages that depend on route_lifecycle_detector