Reputation: 1678
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.
Upvotes: 61
Views: 49938
Reputation: 8038
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'),
);
}
}
Upvotes: 10
Reputation: 2259
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;
}
}
Upvotes: 9
Reputation: 536
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();
}
Upvotes: 38
Reputation: 3652
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();
}
Upvotes: 9
Reputation: 2846
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...
Upvotes: 2