55

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.

Sp4Rx
  • 1,498
  • 3
  • 20
  • 37
  • Not duplicate . I mentioned that I'm not asking about `didChangeAppLifecycleState`. My use case is different @Tokenyet – Sp4Rx Sep 09 '19 at 14:59
  • 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://stackoverflow.com/a/53861303/11020832 – Ahmed AL-Yousif Sep 09 '19 at 15:06
  • 1
    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 :P – Tokenyet Sep 09 '19 at 15:26
  • 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() ? – K Pradeep Kumar Reddy Aug 21 '20 at 13:58

5 Answers5

33

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();
}
Irfandi D. Vendy
  • 894
  • 12
  • 20
Amit
  • 468
  • 5
  • 16
  • Thanks for this info. This is the nearest answer to `onResume` in Android. Marking as correct. – Sp4Rx Oct 25 '19 at 07:04
  • @Amit 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() ? – K Pradeep Kumar Reddy Aug 21 '20 at 13:56
  • 1
    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. – Bliv_Dev Oct 03 '20 at 05:00
  • 1
    Definitely you can declare it as global but as i declared final so I can access that from anywhere in my flutter application – Amit Oct 04 '20 at 06:07
  • @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 route – Amit Apr 20 '21 at 15:07
  • 2
    In `routeObserver.subscribe(this, ModalRoute.of(context));`, `ModalRoute.of(context)` results into `The argument type 'ModalRoute?' can't be assigned to the parameter type 'PageRoute'.` – Feroz Khan Jul 30 '21 at 05:38
  • 1
    @FerozKhan use `observer.subscribe(this, ModalRoute.of(context) as PageRoute);` – Konstantin Kozirev Nov 09 '21 at 16:05
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();
  }
Petro
  • 3,484
  • 3
  • 32
  • 59
8

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;
  }
}
ipcjs
  • 2,082
  • 2
  • 17
  • 20
5

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'),
      );
  }
}
Kyle Venn
  • 4,597
  • 27
  • 41
  • 1
    Just tested this for MacOS and it doesn't work. Only for Mobile. – Oliver Dixon May 20 '22 at 14:33
  • 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' – Sneg Nov 11 '22 at 12:46
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...

Software Person
  • 2,526
  • 13
  • 18
  • 1
    I'm not able to get your answer @software-person and `await Navigator.push(...)` works for me as @Tokenyet mentioned in comments – Sp4Rx Sep 10 '19 at 07:54
  • 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 answer – Software Person Sep 10 '19 at 08:28
  • Though your answer has better approach, I will implement it somewhere and mark as correct. – Sp4Rx Sep 10 '19 at 08:42
  • @KPradeepKumarReddy Sources are my head and [the route class reference](https://api.flutter.dev/flutter/widgets/Route-class.html). – Software Person Aug 20 '20 at 22:08