6

I need to create a login form. After user successfully login than I need to start some kind of timer (ex: 3 min), so if user has no reaction to app or other word if flutter app state is paused, suspended or inactive more than 3 min. the app will goto main login page. As long as user has interaction with app I need to cancel the timer and only I need to star timer app state is paused, suspended or inactive. How do I do that?

I try to implement the "WidgetsBindingObserver" but its look like is not working as I wanted. If user enters successfully and navigate in app the WidgetsBindingObserver fail (error: state object for widget that no longer appears in the widget tree).

My question is how to implement timed-based flutter app lifecycle, as long as user has interaction with the app? If no user interaction the lifecycle timer will start and if before the timer ends there is a user interaction the timer must be canceled.

class _MyUserHomePageState extends State<MyUserHomePage> with WidgetsBindingObserver {

  AppLifecycleState _appLifecycleState;



@override
void initState() {
  _appStatePasue = false;
  WidgetsBinding.instance.addObserver(this);
  super.initState();
}


// TODO: DID_CHANGE_APP_LIFE_CYCLE
void didChangeAppLifecycleState(AppLifecycleState state) {
  setState(() {
    _appLifecycleState = state;
    if(_appLifecycleState == AppLifecycleState.paused ||
        _appLifecycleState == AppLifecycleState.inactive ||
        _appLifecycleState == AppLifecycleState.suspending) {
      _appStatePasue = true;
      print("timer---fired: $_appLifecycleState");
      _timer = Timer.periodic(Duration(minutes: 1), _capitalCallback);
      print(_appLifecycleState);
    } else {
      _appStatePasue = false;
    }
  });
}

// TODO: APP_LIFE_CYCLE__CALLBACK
void _capitalCallback(_timer) {
  if(_appStatePasue == true) {
    _timer.cancel();
    print("return---main---page: $_appLifecycleState");
    setState(() {
      Navigator.push(
          context,
          SlideRightRoute(widget: MyApp())
      );
    });
  } else {
    _timer.cancel();
    print("timer---canceled: $_appLifecycleState");
  }
}


@override
void dispose() {
  super.dispose();
}

@override
void onDeactivate() {
  super.deactivate();
}

@override
Widget build(BuildContext context) {
    return new Scaffold (

    );
}

}
Nick
  • 4,163
  • 13
  • 38
  • 63

4 Answers4

8

You can use the Timer class to trigger a log out function after 3 minutes of inactivity. Something you can try is to wrap your entire app in a GestureDetector that resets the timer on any event. You'd just have to make sure that any other GestureDetectors in your app use HitTestBehavior.translucent so the events are propagated to your root listener. Here's a full example:

import 'dart:async';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => AppRoot();
}

class AppRoot extends StatefulWidget {
  @override
  AppRootState createState() => AppRootState();
}

class AppRootState extends State<AppRoot> {
  Timer _timer;

  @override
  void initState() {
    super.initState();

    _initializeTimer();
  }

  void _initializeTimer() {
    _timer = Timer.periodic(const Duration(minutes: 3), (_) => _logOutUser);
  }

  void _logOutUser() {
    // Log out the user if they're logged in, then cancel the timer.
    // You'll have to make sure to cancel the timer if the user manually logs out
    //   and to call _initializeTimer once the user logs in
    _timer.cancel();
  }

  // You'll probably want to wrap this function in a debounce
  void _handleUserInteraction([_]) {
    if (!_timer.isActive) {
      // This means the user has been logged out
      return;
    }

    _timer.cancel();
    _initializeTimer();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleUserInteraction,
      onPanDown: _handleUserInteraction,
      onScaleStart: _handleUserInteraction,
      // ... repeat this for all gesture events
      child: MaterialApp(
        // ... from here it's just your normal app,
        // Remember that any GestureDetector within your app must have
        //   HitTestBehavior.translucent
      ),
    );
  }
}

UPDATE: I just discovered the Listener class which might make more sense here than the GestureDetector. I've personally never used it, but feel free to experiment! Check out the documentation on gestures for more info.

Kirollos Morkos
  • 2,503
  • 16
  • 22
  • Markos thanks for the info. I have more than 40 pages, so how do I wrap entire app in a GestureDetector? – Nick Oct 03 '18 at 05:16
  • Just make it one of your root level widgets. I'll post a full example shortly. – Kirollos Morkos Oct 03 '18 at 16:37
  • thanks for the info. Only "HitTestBehavior.translucent" part I didn't understand? How to implement this for other pages? – Nick Oct 04 '18 at 11:30
  • I got it. Thank you very much :) – Nick Oct 04 '18 at 12:18
  • Thanks @Kirollos, Timer is working, but I cannot navigate to login page. I can remove the session credentials for users but need to re-direct to login page. Any idea? – Nick Oct 04 '18 at 13:34
  • What do you mean? Can you provide more context? – Kirollos Morkos Oct 04 '18 at 13:55
  • In void _logOutUser() I cancel timer, clear the user credentials (after login saved securely in device and remove when user logout) and try to use Navigator to jump Login/user_login.dart. I get context error that not belong to tree yet... :( – Nick Oct 04 '18 at 13:57
  • Ah I see. In the example I gave, AppRoot is the top level widget so it doesn't have a Navigator in it's hierarchy. You will probably want to move things around, I was just providing an example. – Kirollos Morkos Oct 04 '18 at 14:01
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/181303/discussion-between-kirollos-morkos-and-nick). – Kirollos Morkos Oct 04 '18 at 18:44
  • @Nick, did you end up coming up with a solution to your problem? I would like to know how you did it. – deantawonezvi Nov 11 '19 at 08:18
  • This is not working for me, please help me https://stackoverflow.com/questions/67598039/how-do-i-logout-time-based-in-flutter-app – Gursewak Singh May 25 '21 at 10:27
1

Update to Kirollos Morkos's Answer We have used NavigatorState key to logout.

Here is the full code of AppRootState.

class AppRootState extends State<AppRoot> {
  Timer _timer;
  bool forceLogout = false;
  final navigatorKey = GlobalKey<NavigatorState>();

  @override
  void initState() {
    super.initState();

    _initializeTimer();
  }

  void _initializeTimer() {
    _timer = Timer.periodic(const Duration(minutes: 10), (_) => _logOutUser());
  }

  void _logOutUser() {
    // Log out the user if they're logged in, then cancel the timer.
    // You'll have to make sure to cancel the timer if the user manually logs out
    //   and to call _initializeTimer once the user logs in
    _timer.cancel();
    setState(() {
      forceLogout = true;
    });
  }

  // You'll probably want to wrap this function in a debounce
  void _handleUserInteraction([_]) {
    print("_handleUserInteraction");
    _timer.cancel();
    _initializeTimer();
  }

  void navToHomePage(BuildContext context) {
    //Clear all pref's
    SharedPreferencesHelper.clearAllValues();

    navigatorKey.currentState.pushAndRemoveUntil(
        MaterialPageRoute(builder: (context) => LoginPage()),
        (Route<dynamic> route) => false);
  }

  @override
  Widget build(BuildContext context) {
    if (forceLogout) {
      print("ForceLogout is $forceLogout");
      navToHomePage(context);
    }
    return GestureDetector(
        onTap: _handleUserInteraction,
        onPanDown: _handleUserInteraction,
        onScaleStart: _handleUserInteraction,

        // ... repeat this for all gesture events
        child: MaterialApp(
          navigatorKey: navigatorKey,
          // ...
          // ...
          ));
  }
}
Gunaseelan
  • 14,415
  • 11
  • 80
  • 128
0

For anyone having issues with Navigating, Simple create class with a context as static parameter and then set the context from any of your first widgets in the app, then you can use the context in your timeout function create class:

class ContextClass{ static BuildContext CONTEXT; }

set the context from any of your first widget build method like so

    ContextClass.CONTEXT=context;

And use in your time out function like so

        Navigator.of(ContextClass.CONTEXT).pushNamedAndRemoveUntil('<Your Route>', (Route<dynamic> route) => false);
Dharman
  • 30,962
  • 25
  • 85
  • 135
Gstuntz
  • 434
  • 4
  • 10
-1

To access Timer for every screen as well as close all the screens after session timeout and open Login Screen.

Define Session Expiry time at separately Constants.dart file as static.

static const int sessionExpireTimeout = 30; //in seconds

Now after Successful login, at next screen i.e. HomeScreen(), initialize a method called Future.delayed() with expiry time inside Widget build(BuildContext context) method:

Future.delayed(const Duration(seconds: Constants.sessionTimeout), () async {
      await FirebaseAuth.instance.signOut(); // Firebase Sign out before exit
      // Pop all the screens and Pushes Login Screen only
      Navigator.of(context)
          .pushNamedAndRemoveUntil(LoginScreen(), (route) => false);
    });

Remember that you don't have to Pop this HomeScreen() while using Navigator. Whenever you want to navigate to another screen. Use pushNamed() or push() method. Then after switching to another screen, you can use any Navigator method.