如何创建基于时间的 Flutter 应用程序?

How do I create a time-based Flutter App?

我需要创建一个登录表单。用户成功登录后,我需要启动某种计时器(例如:3 分钟),因此如果用户对应用程序或其他词没有反应,如果 flutter 应用程序状态暂停、挂起或不活动超过 3 分钟。该应用程序将转到主登录页面。只要用户与应用程序进行交互,我就需要取消计时器,并且只需要启动计时器应用程序状态为暂停、暂停或非活动状态。我该怎么做?

我尝试实现 "WidgetsBindingObserver" 但它看起来不像我想要的那样工作。如果用户成功进入并在应用程序中导航,WidgetsBindingObserver 将失败(错误:小部件的状态对象不再出现在小部件树中)。

我的问题是如何实现基于时间的 flutter 应用程序生命周期,只要用户与应用程序有交互?如果没有用户交互,生命周期计时器将启动,如果在计时器结束之前有用户交互,则必须取消计时器。

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 (

    );
}

}

您可以使用 Timer class 在 3 分钟不活动后触发注销功能。您可以尝试将整个应用程序包装在一个 GestureDetector 中,以便在任何事件时重置计时器。您只需确保您的应用程序中的任何其他 GestureDetectors 使用 HitTestBehavior.translucent,以便将事件传播到您的根侦听器。这是一个完整的例子:

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
      ),
    );
  }
}

更新:我刚刚发现 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 以获取更多信息。

对于导航有问题的任何人,只需创建 class 并将上下文作为静态参数,然后从应用程序中的任何第一个小部件设置上下文,然后您就可以在超时函数中使用上下文 创建 class:

class ContextClass{ static BuildContext CONTEXT; }

像这样从您的任何第一个小部件构建方法设置上下文

    ContextClass.CONTEXT=context;

然后像这样在你的超时函数中使用

        Navigator.of(ContextClass.CONTEXT).pushNamedAndRemoveUntil('<Your Route>', (Route<dynamic> route) => false);

访问每个屏幕的计时器以及在会话超时后关闭所有屏幕并打开登录屏幕。

Constants.dart 文件中单独定义会话到期时间为 static.

static const int sessionExpireTimeout = 30; //in seconds

现在成功登录后,在下一个屏幕,即 HomeScreen(),初始化一个名为 Future.delayed() 的方法Widget build(BuildContext context) 方法中的到期时间:

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);
    });

记住您不必在使用 Navigator 时弹出此 HomeScreen()。 每当您想导航到另一个屏幕时。使用 pushNamed() 或 push() 方法。 然后切换到另一个屏幕后,您可以使用任何导航器方法。

更新为 我们已经使用 NavigatorState 键退出。

这里是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,
          // ...
          // ...
          ));
  }
}