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

6
我需要创建一个登录表单。用户成功登录后,我需要启动某种计时器(例如: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 (

    );
}

}
4个回答

8
你可以使用 Timer 类来在3分钟的不活动后触发注销功能。你可以尝试将整个应用程序包装在一个 GestureDetector 中,在任何事件上重置计时器。你只需要确保应用程序中的任何其他 GestureDetector 使用 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类,它可能比GestureDetector更适合这里。 我个人从未使用过它,但可以自由尝试!请查看手势文档获取更多信息。


Markos,感谢您的信息。我的应用程序有超过40页,那么我如何将整个应用程序包装在GestureDetector中? - Nick
只需将其作为根级别的小部件之一即可。我很快会发布一个完整的示例。 - Kirollos Morkos
谢谢提供信息。只有"HitTestBehavior.translucent"这部分我没太明白,如何在其他页面实现呢? - Nick
我明白了。非常感谢 :) - Nick
你的意思是什么?你能提供更多的背景信息吗? - Kirollos Morkos
显示剩余6条评论

1

更新至 Kirollos Morkos的答案 我们使用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,
          // ...
          // ...
          ));
  }
}

0

对于任何在导航方面遇到问题的人,可以简单地创建一个带有上下文作为静态参数的类,然后从应用程序中的任何第一个小部件设置上下文,接着就可以在超时函数中使用上下文了。

创建类:

class ContextClass{ static BuildContext CONTEXT; }

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

    ContextClass.CONTEXT=context;

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

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

-1

为了在每个屏幕上访问计时器,并在会话超时后关闭所有屏幕并打开登录屏幕。

在单独的Constants.dart文件中定义会话过期时间为静态

static const int sessionExpireTimeout = 30; //in seconds

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

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

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


1
你能不能不把几乎每个段落都加粗呢? - Luke_

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接