How to check when my widget screen comes to visibility in flutter like onResume in Android
Asked Answered
F

5

57

In android if an activity is visible onResume is called. What is the equivalent method of onResume in Flutter?

I need the know when my widget screen is visible so I can auto-play a video based on that. I might go to another widget screen an when I come back it should auto-play.

My approach was to play the video in didUpdateWidget but didUpdateWidget is called every-time even the widget screen is not visible.

Note: I'm not asking about didChangeAppLifecycleState from WidgetsBindingObserver as it gives onResume etc callbacks for the app lifecycle not a particular widget screen.

Fibroid answered 9/9, 2019 at 14:57 Comment(4)
Not duplicate . I mentioned that I'm not asking about didChangeAppLifecycleState. My use case is different @TokenyetFibroid
When you are in the other screen, if you pop back to the video screen you can pass a parameter in the Navigator.pop(context, parameter), so in the video screen you will get the parameter and do something accordingly. See this post https://mcmap.net/q/161707/-passing-data-between-screens-in-flutterTransitive
Oh, I finally know you are not taking about onResume. It's more about PageTransition, and look like you have the workaround. For not talking too much code, I prefer to stop before Navigator.push and await Navigator.pop to resume video. If you are not using Navigator, you might need to provide more code to show us :PSurinam
In the below answer which method is equivalent to onPause() and which method is equivalent to onResume() ? I'm assuming didPush() is similar to onResume() and didPop() is similar to onPause(). Am i correct ? Is my understanding correct ? I'm having confusion between didPopNext() and didPop(). Is didPopNext() also same as onResume() ?Timeconsuming
B
35

All of the problems are solved.

Put an observer on the navigator from the root of the widget tree (materialappwidget).

If you need more explanation please follow this link: https://api.flutter.dev/flutter/widgets/RouteObserver-class.html

I have implemented in my project and its working great @Sp4Rx

// Register the RouteObserver as a navigation observer.
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();

void main() {
  runApp(MaterialApp(
    home: Container(),
    navigatorObservers: [routeObserver],
  ));
}

class RouteAwareWidget extends StatefulWidget {
  State<RouteAwareWidget> createState() => RouteAwareWidgetState();
}

// Implement RouteAware in a widget's state and subscribe it to
// the
// RouteObserver.
class RouteAwareWidgetState extends State<RouteAwareWidget> with RouteAware {
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    routeObserver.subscribe(this, ModalRoute.of(context));
  }

  @override
  void dispose() {
    routeObserver.unsubscribe(this);
    super.dispose();
  }

  @override
  void didPush() {
    // Route was pushed onto navigator and is now topmost route.
  }

  @override
  void didPopNext() {
    // Covering route was popped off the navigator.
  }

  @override
  Widget build(BuildContext context) => Container();
}
Bogus answered 22/10, 2019 at 12:34 Comment(8)
Thanks for this info. This is the nearest answer to onResume in Android. Marking as correct.Fibroid
@Bogus Here which method is equivalent to onPause() and which method is equivalent to onResume() ? I'm assuming didPush() is similar to onResume() and didPop() is similar to onPause(). Am i correct ? Is my understanding correct ? I'm having confusion between didPopNext() and didPop(). Is didPopNext() also same as onResume() ?Timeconsuming
Good one! however it might be suggested to define the observer somwhere where it can be accessed from every where in the app like a global attribute.Trumpetweed
Definitely you can declare it as global but as i declared final so I can access that from anywhere in my flutter applicationBogus
@KPradeepKumarReddy in flutter it is not that simple as android the concept is about route add to tree and route remove from tree. you can check lifecycle of flutter routeBogus
In routeObserver.subscribe(this, ModalRoute.of(context));, ModalRoute.of(context) results into The argument type 'ModalRoute<Object?>?' can't be assigned to the parameter type 'PageRoute<dynamic>'.Ivatts
@FerozKhan use observer.subscribe(this, ModalRoute.of(context) as PageRoute);Commode
didPush -> When the current route is pushed (viewDidLoad + viewDidAppear) didPop -> When the current route is popped (viewDidDisappear) didPushNext -> When another route is pushed on top the current route (viewDidDisappear) didPopNext -> When the next route which was directly next to current one gets popped (viewWillAppear or viewDidAppear) I have given iOS equivalent lifecycle methods.Sunfish
C
9

I struggled to get a video to pause when not viewing the main screen of my app. I applied this VisibilityDetector and grabbed the visiblePercentage to force a pause or resume:

VisibilityDetector(
    key: Key('visible-video--key-${this.randomkeygenerator}-1'),
    onVisibilityChanged: (visibilityInfo) {
      var visiblePercentage = visibilityInfo.visibleFraction * 100;

      if (visiblePercentage < 1){ //the magic is done here
        if(_video_controller != null) {
          if(disposed_vid == false) {
            _video_controller.pause();
          }
        }

      }else{
        if(_video_controller != null) {
          if(disposed_vid == false) {
            _video_controller.play();
          }
        }
      }
      debugPrint(
          'Widget ${visibilityInfo.key} is ${visiblePercentage}% visible');
    },
    child: VideoPlayer(_video_controller)),


  @override
  void dispose() {
    // If the video is playing, pause it.
    _video_controller .pause();
    _video_controller .dispose();
    disposed_vid = true;
    super.dispose();
  }
Carrizales answered 28/5, 2020 at 19:29 Comment(3)
Do you have videos inside a list? @petroFibroid
Ya, PageView actuallyCarrizales
Then this implementation is fine. But my query was different.Fibroid
V
9

Because the animation of the background route will be disabled. So we can judge whether it is in the foreground in this way:

final isForeground = TickerMode.of(context);

Wrap it into a widget:

/// Created by ipcjs on 2021/3/23.
class ForegroundDetector extends StatefulWidget {
  const ForegroundDetector({
    Key? key,
    required this.child,
    required this.onForegroundChanged,
  }) : super(key: key);

  final ValueChanged<bool> onForegroundChanged;
  final Widget child;

  @override
  ForegroundDetectorState createState() => ForegroundDetectorState();
}

class ForegroundDetectorState extends State<ForegroundDetector> {
  bool get isForeground => _isForeground ?? false;
  bool? _isForeground;

  @override
  Widget build(BuildContext context) {
    final isForeground = TickerMode.of(context);
    if (_isForeground != isForeground) {
      _isForeground = isForeground;
      widget.onForegroundChanged(isForeground);
    }
    return widget.child;
  }
}
Vezza answered 23/3, 2021 at 12:6 Comment(2)
I'm busy with other work actually. I will need some time to verify. Anyways this seems promising.Fibroid
Thanks, exactly that I need.Unready
S
8

None of these existing questions exactly answered the question for me, so I wrote up a more thorough answer here which talks about how to get all the same lifecycle methods as iOS and Android.

But the gist: I recommend using the FocusDetector package. It works exactly like onResume and onPause. It would be implemented as follows.

class PageState extends State<Page> {

  void onResume() {
    log("onResume / viewWillAppear / onFocusGained");
  }

  void onPause() {
    log("onPause / viewWillDisappear / onFocusLost");
  }

  @override
  Widget build(BuildContext context) {
    return FocusDetector(
      onFocusGained: onResume,
      onFocusLost: onPause,
      child: Text('Rest of my widget'),
      );
  }
}
Semang answered 18/3, 2022 at 17:42 Comment(2)
Just tested this for MacOS and it doesn't work. Only for Mobile.Pumping
Looks like a great solution, but not compatible with latest version of Google mobile ads. Receive error: 'Because focus_detector >=2.0.1 depends on visibility_detector ^0.2.2 and google_mobile_ads 2.2.0 depends on visibility_detector ^0.3.3, focus_detector >=2.0.1 is incompatible with google_mobile_ads 2.2.0'Flaunt
O
2

It's probably not the simplest and definitely not perfect, but a while back I implemented events like those with routes. Basically, EventRoute<T> is a drop-in replacement for MaterialPageRoute<T> that provides optional callbacks for when the Widget is created, pushed to the foreground, pushed to the background and when it gets popped off.

event_route.dart:

import 'package:flutter/material.dart';

enum RouteState {
  none,
  created,
  foreground,
  background,
  destroyed
}

class EventRoute<T> extends MaterialPageRoute<T> {
  BuildContext _context;
  RouteState _state;
  Function(BuildContext) _onCreateCallback;
  Function(BuildContext) _onForegroundCallback;
  Function(BuildContext) _onBackgroundCallback;
  Function(BuildContext) _onDestroyCallback;

  EventRoute(BuildContext context, {
    builder,
    RouteSettings settings,
    bool maintainState = true,
    bool fullscreenDialog = false,
    Function(BuildContext) onCreate,
    Function(BuildContext) onForeground,
    Function(BuildContext) onBackground,
    Function(BuildContext) onDestroy
  }):
        _context = context,
        _onCreateCallback = onCreate,
        _onForegroundCallback = onForeground,
        _onBackgroundCallback = onBackground,
        _onDestroyCallback = onDestroy,
        _state = RouteState.none,
        super(builder: builder, settings: settings, maintainState: maintainState, fullscreenDialog: fullscreenDialog);


  void get state => _state;

  @override
  void didChangeNext(Route nextRoute) {
    if (nextRoute == null) {
      _onForeground();
    } else {
      _onBackground();
    }
    super.didChangeNext(nextRoute);
  }

  @override
  bool didPop(T result) {
    _onDestroy();
    return super.didPop(result);
  }

  @override
  void didPopNext(Route nextRoute) {
    _onForeground();
    super.didPopNext(nextRoute);
  }

  @override
  TickerFuture didPush() {
    _onCreate();
    return super.didPush();
  }

  @override
  void didReplace(Route oldRoute) {
    _onForeground();
    super.didReplace(oldRoute);
  }

  void _onCreate() {
    if (_state != RouteState.none || _onCreateCallback == null) {
      return;
    }
    _onCreateCallback(_context);
  }

  void _onForeground() {
    if (_state == RouteState.foreground) {
      return;
    }
    _state = RouteState.foreground;
    if (_onForegroundCallback != null) {
      _onForegroundCallback(_context);
    }
  }

  void _onBackground() {
    if (_state == RouteState.background) {
      return;
    }
    _state = RouteState.background;
    if (_onBackgroundCallback != null) {
      _onBackgroundCallback(_context);
    }
  }

  void _onDestroy() {
    if (_state == RouteState.destroyed || _onDestroyCallback == null) {
      return;
    }
    _onDestroyCallback(_context);
  }
}

And then to push your route you do:

Navigator.push(context, EventRoute(context, builder: (context) => YourWidget(context),
      onCreate: (context) => print('create'),
      onForeground: (context) => print('foreground'),
      onBackground: (context) => print('background'),
      onDestroy: (context) => print('destroy')
));

The context is a little icky though...

Ol answered 9/9, 2019 at 15:10 Comment(4)
I'm not able to get your answer @software-person and await Navigator.push(...) works for me as @Surinam mentioned in commentsFibroid
I created an EventRoute class. It is a class with which provides optional callbacks for when the Widget is created, pushed to the foreground, pushed to the background and when it gets popped off. All those events are optional. You can basically replace a MaterialPageRoute with this class and capture those events. But if @Toeknyet his/her solution works for you, you can use that one as well. I updated my answerOl
Though your answer has better approach, I will implement it somewhere and mark as correct.Fibroid
@KPradeepKumarReddy Sources are my head and the route class reference.Ol

© 2022 - 2024 — McMap. All rights reserved.