Daibaku
Daibaku

Reputation: 12566

Detect if the user leaves the current page in Flutter?

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

Answers (6)

Michael Kjellander
Michael Kjellander

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

Alisha khan
Alisha khan

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

Viren V Varasadiya
Viren V Varasadiya

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

Tonny Bawembye
Tonny Bawembye

Reputation: 576

You can achieve this by overriding the dispose() method.

Upvotes: 3

nonybrighto
nonybrighto

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

Marcel
Marcel

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

Related Questions