title: Flutter | How to know if widget is on top of navigation stack (visible)
body: <p>In my Flutter app, I wish to pause an animation if a new page is pushed to the navigation stack.</p>
  <p><strong>Question:</strong> From a widgets perspective, is there any way to know if the widget is at the top of the navigation stack?</p>
  <p>Example:</p>
  <pre><code>while (isTopOfNavigationStack) {
      // Do the animation
  }
  </code></pre>
  <p><em>Edit 1: More details</em></p>
  <p>Im using the library <a href="https://pub.dev/packages/widget_marquee" rel="nofollow noreferrer">widget_marquee</a> for creating a horizontal ticker. This library works great until a new page is pushed to the navigation stack. When this happens, the <code>while-loop</code> becomes infinite and the application freezes.</p>
  <pre><code>library widget_marquee;
  
  import 'dart:developer';
  import 'package:flutter/material.dart';
  
  /// Rotates the [child] widget indefinitely along the horizontal axis if the
  /// content extends pass the edge of the render area.
  ///
  /// [delayDuration] - One time delay to wait before starting the text rotation
  /// [gap] - Spacing to add between widget end and start
  /// [loopDuration] - Time for one full rotation of the child
  /// [onLoopFinish] - Function to run upon finishing each loop
  /// [onScrollingTap]
  /// [pixelsPerSecond] - Alternate to loop duration
  class Marquee extends StatelessWidget {
    const Marquee({
      Key? key,
      required this.child,
      this.delayDuration = const Duration(milliseconds: 1500),
      this.gap = 50,
      this.loopDuration = const Duration(milliseconds: 8000),
      this.onLoopFinish = _onLoopFinish,
      this.onScrollingTap = _onScrollingTap,
      this.pixelsPerSecond = 0,
    }) : super(key: key);
  
    final Widget child;
    final Duration delayDuration;
    final double gap;
    final Duration loopDuration;
    final Future&lt;void&gt; Function() onLoopFinish;
    final Future&lt;void&gt; Function() onScrollingTap;
    final int pixelsPerSecond;
  
    @override
    Widget build(BuildContext context) {
      return _Marquee(
        key: UniqueKey(),
        child: child,
        delay: delayDuration,
        gap: gap,
        loopDuration: loopDuration,
        onLoopFinish: onLoopFinish,
        onScrollingTap: onScrollingTap,
        pps: pixelsPerSecond,
      );
    }
  }
  
  class _Marquee extends StatefulWidget {
    const _Marquee({
      required Key key,
      required this.child,
      required this.delay,
      required this.gap,
      required this.loopDuration,
      required this.onLoopFinish,
      required this.onScrollingTap,
      required this.pps,
    }) : super(key: key);
  
    final Widget child;
    final Duration delay;
    final double gap;
    final Duration loopDuration;
    final Future&lt;void&gt; Function() onLoopFinish;
    final Future&lt;void&gt; Function() onScrollingTap;
    final int pps;
  
    @override
    _MarqueeState createState() =&gt; _MarqueeState();
  }
  
  class _MarqueeState extends State&lt;_Marquee&gt; with TickerProviderStateMixin {
    late double contentArea;
    bool isScrolling = false;
    late ScrollController scrollController;
    List&lt;Widget&gt; widgets = &lt;Widget&gt;[];
  
  
    @override
    void initState() {
      super.initState();
    }
  
    @override
    void didChangeDependencies() {
      scrollController = ScrollController(
        initialScrollOffset: 0.0,
        keepScrollOffset: false,
      );
  
      widgets = &lt;Widget&gt;[widget.child];
  
      // Initialize the scroll controller
  
      WidgetsBinding.instance?.addPostFrameCallback(scroll);
      super.didChangeDependencies();
    }
  
    void scroll(_) async {
      if (scrollController.position.maxScrollExtent &gt; 0) {
        late Duration duration;
        final double initMax = scrollController.position.maxScrollExtent;
  
        // Add a sized box and duplicate widget to the row
        setState(() {
          widgets.add(SizedBox(width: widget.gap));
          widgets.add(widget.child);
        });
  
        await Future&lt;dynamic&gt;.delayed(widget.delay);
  
        try {
          setState(() {
            isScrolling = true;
          });
  
          while (scrollController.hasClients) {
  
            // Calculate the position where the duplicate widget lines up with the original
            final scrollExtent =
                (initMax * 2) - (initMax - contentArea) + widget.gap;
  
            // Set the duration of the animation
            if (widget.pps &lt;= 0) {
              duration = widget.loopDuration;
            } else {
              duration = Duration(
                // Calculate the duration based on the pixels per second
                milliseconds: ((scrollExtent / widget.pps) * 1000).toInt(),
              );
            }
  
            await scrollController.animateTo(
              scrollExtent,
              duration: duration,
              curve: Curves.linear,
            );
  
            // Jump to the beginning of the view to imitate loop
            scrollController.jumpTo(0);
            await widget.onLoopFinish();
          }
        } catch (e) {
          log('Marquee element has been disposed');
        }
      }
    }
  
    @override
    Widget build(BuildContext context) {
      return LayoutBuilder(
        builder: (BuildContext context, BoxConstraints constraints) {
          contentArea = constraints.maxWidth;
  
          // Thanks to how widgets work, the gesture detector is only triggered
          // if there's nothing clickable in the child
          return GestureDetector(
            onTap: () async {
              if (isScrolling) {
                await widget.onScrollingTap();
              }
            },
            child: Container(
              alignment: Alignment.center,
              child: SingleChildScrollView(
                physics: const NeverScrollableScrollPhysics(),
                child: Row(
                  children: widgets,
                ),
                scrollDirection: Axis.horizontal,
                controller: scrollController,
              ),
            ),
          );
        },
      );
    }
  
    @override
    void dispose() {
      scrollController.dispose();
      super.dispose();
    }
  }
  
  Future&lt;void&gt; _onLoopFinish() async {}
  
  Future&lt;void&gt; _onScrollingTap() async {
    log('Marquee onScrollingTap function triggered');
  }
  </code></pre>