Reputation: 12566
Is there any way to detect if the user leaves the current page?
I don’t think WidgetsBinding
will work, because it handles these events by itself.
So, does anyone have any solution?
Any help is appreciated.
Upvotes: 39
Views: 34949
Reputation: 349
RouteObserver and RouteAware are the way to go if you want to detect if the user leaves the screen, no matter if it's by going back (popping) or pushing another context.
Here's an example. Sorry for it being it a bit long, if you try it and run it you'll realize that it's pretty simple.
import 'package:flutter/material.dart';
void main() async {
runApp(App());
}
class App extends StatelessWidget {
static final RouteObserver<PageRoute> routeObserver =
RouteObserver<PageRoute>();
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
navigatorObservers: [routeObserver],
routes: {
'/': (context) => Screen1(),
'screen2': (context) => Screen2(),
},
);
}
}
class ScreenWrapper extends StatefulWidget {
final Widget child;
final Function() onLeaveScreen;
final String routeName;
ScreenWrapper({this.child, this.onLeaveScreen, @required this.routeName});
@override
State<StatefulWidget> createState() {
return ScreenWrapperState();
}
}
class ScreenWrapperState extends State<ScreenWrapper> with RouteAware {
@override
Widget build(BuildContext context) {
return widget.child;
}
void onLeaveScreen() {
if (widget.onLeaveScreen != null) {
widget.onLeaveScreen();
}
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
App.routeObserver.subscribe(this, ModalRoute.of(context) as PageRoute);
}
@override
void dispose() {
super.dispose();
App.routeObserver.unsubscribe(this);
}
@override
void didPush() {
print('*** Entering screen: ${widget.routeName}');
}
void didPushNext() {
print('*** Leaving screen: ${widget.routeName}');
onLeaveScreen();
}
@override
void didPop() {
print('*** Going back, leaving screen: ${widget.routeName}');
onLeaveScreen();
}
@override
void didPopNext() {
print('*** Going back to screen: ${widget.routeName}');
}
}
class Screen1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ScreenWrapper(
onLeaveScreen: () {
print("***** Here's my special handling for leaving screen1!!");
},
routeName: '/',
child: Scaffold(
backgroundColor: Colors.yellow,
body: SafeArea(
child: Column(
children: [
Text('This is Screen1'),
FlatButton(
child: Text('Press here to go to screen 2'),
onPressed: () {
Navigator.pushNamed(context, 'screen2');
},
),
FlatButton(
child: Text(
"Press here to go back (only works if you've pushed before)"),
onPressed: () {
Navigator.maybePop(context);
},
),
],
),
),
),
);
}
}
class Screen2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ScreenWrapper(
routeName: 'screen2',
child: Scaffold(
backgroundColor: Colors.blue,
body: SafeArea(
child: Column(
children: [
Text('This is Screen2'),
FlatButton(
child: Text('Press here to go to screen 1'),
onPressed: () {
Navigator.pushNamed(context, '/');
},
),
FlatButton(
child: Text(
"Press here to go back (only works if you've pushed before)"),
onPressed: () {
Navigator.maybePop(context);
},
),
],
),
),
),
);
}
}
Upvotes: 19
Reputation: 1
For Web part use below code to detect user active or not
if (kIsWeb) {
EasyLoading.init();
window.addEventListener('focus', onFocus);
window.addEventListener('blur', onBlur);
} else {
WidgetsBinding.instance.addObserver(this);
}
Upvotes: 0
Reputation: 27147
As per your description i think that you want to track your user if user press back button or return to previous screen.
You can achieve this by overriding the dispose
on your State
class.
May Following example help you to figure out your solution.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: EventRow(),
);
}
}
class EventRow extends StatelessWidget {
@override
Widget build (BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: new Text("Demo"),
),
body: Center(
child: Container(
child: new RaisedButton(
onPressed: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
},
child: Text("Goto Second Scrren"),
),
),
),
);
}
}
class SecondScreen extends StatefulWidget {
@override
_SecondScreenState createState() => _SecondScreenState();
}
class _SecondScreenState extends State<SecondScreen> {
@override
void initState() {
// TODO: implement initState
super.initState();
}
@override
void dispose() {
// TODO: implement dispose
print("Back To old Screen");
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: new Center(
child: new Container(
child: new RaisedButton(
child: Text("Goto First Scrren"),
onPressed: (){
Navigator.pop(context);
}
),
),
),
);
}
}
Upvotes: 20
Reputation: 9581
I recently got into a situation where I needed to detect a change in route for both popping the current route or pushing to a new route from the current one and I was able to achieve that with RouteObserver gotten from the answer here.
With WillPopScope
you can detect a possible change in route (pop) before it occurs and determine if it should be allowed or declined. If you simply want to carry out an action after pop, it can be done by overriding onDispose
.
Upvotes: 1
Reputation: 9569
I have an easy solution if by "leaving the page" you mean that the user goes "back" from this page. The following solution will not work if you also want to get notified if the user opens up another page in front of the current one.
For the first case, you could use a WillPopScope.
It's a class that notifies you when the enclosing ModalRoute
(internally used by the Navigator
) is about to be popped. It even leaves you a choice to whether or not you want the pop to happen.
Just wrap the second screen's Scaffold
in a WillPopScope
.
return WillPopScope(
onWillPop: () async {
// You can do some work here.
// Returning true allows the pop to happen, returning false prevents it.
return true;
},
child: ... // Your Scaffold goes here.
);
Upvotes: 20