【问题标题】:How to execute a function after a period of inactivity in Flutter如何在 Flutter 中一段时间​​不活动后执行函数
【发布时间】:2020-03-16 07:35:59
【问题描述】:

我需要创建一个应用程序,在用户闲置一段时间后导航到该应用程序。

我尝试将我的应用程序包装在 GestureDetector 中,并在指定时间段后运行计时器,如下所示,但它不起作用:

class _MyAppState extends State<MyApp> {
  Timer _timer;

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

    _initializeTimer();
  }

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

  void _logOutUser() {
Navigator.push(
        context,
        MaterialPageRoute(
            builder: (context) => WelcomePage()));
    _timer.cancel();
  }

  void _handleUserInteraction([_]) {
    if (!_timer.isActive) {
      return;
    }

    _timer.cancel();
    _initializeTimer();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleUserInteraction,
      onPanDown: _handleUserInteraction,
      child: MaterialApp(
        title: 'Hello',
        home: WelcomePage(),
        routes: <String, WidgetBuilder>{
          '/welcome': (BuildContext context) => new WelcomePage(),
          '/login': (BuildContext context) => new LoginPage(),
        },
        debugShowCheckedModeBanner: false,
      ),
    );
  }
}

我的问题是,在我的颤振应用程序中一段时间​​不活动后,我应该如何最好地实现运行功能?

【问题讨论】:

    标签: flutter dart timer


    【解决方案1】:

    FWIW,我按照建议尝试使用GestureDetector,但没有按预期工作。问题是当没有活动时,我会收到连续的手势流。这包括当我尝试更严格的 onTap: 回调时。

    我在模拟器的调试模式下看到了这个。我没有进一步实验来查看真实手机是否表现出相同的行为,因为即使它们现在没有,它们将来也可能:显然没有规范保证 GestureDetector 不会接收虚假事件。对于与安全相关的事情,例如不活动超时,这是不可接受的。

    对于我的用例,我认为可以检测应用程序何时不可见超过设定的时间。我对预期用途的推理是,真正的危险是当应用程序不可见时,他们忘记了它的存在。

    设置这种不活动超时非常简单。我安排在应用程序访问敏感信息时(例如输入密码后)致电startKeepAlive()。就我的使用而言,超时后退出应用程序就可以了;显然,如果需要,可以变得更复杂。 Anyhoo,这是相关代码:

    const _inactivityTimeout = Duration(seconds: 10);
    Timer _keepAliveTimer;
    
    void _keepAlive(bool visible) {
      _keepAliveTimer?.cancel();
      if (visible) {
        _keepAliveTimer = null;
      } else {
        _keepAliveTimer = Timer(_inactivityTimeout, () => exit(0));
      }
    }
    
    class _KeepAliveObserver extends WidgetsBindingObserver {
      @override didChangeAppLifecycleState(AppLifecycleState state) {
        switch(state) {
          case AppLifecycleState.resumed:
            _keepAlive(true);
            break;
          case AppLifecycleState.inactive:
          case AppLifecycleState.paused:
          case AppLifecycleState.detached:
            _keepAlive(false);  // Conservatively set a timer on all three
            break;
        }
      }
    }
    
    /// Must be called only when app is visible, and exactly once
    void startKeepAlive() {
      assert(_keepAliveTimer == null);
      _keepAlive(true);
      WidgetsBinding.instance.addObserver(_KeepAliveObserver());
    }
    
    

    在生产中,我可能会延长超时时间 :-)

    【讨论】:

    • 顺便说一句,我最终还检测到屏幕何时因不活动而关闭,以提高安全性。 pub.dev/packages/screen_state 有一个插件,但目前只在 Android 上实现。
    • 你能不能提一下如果应用程序在后台然后10分钟后它会自动清除会话的过程
    • “自动清除[ing]会话”的含义取决于您的应用程序。在上面的代码中,我只调用了exit(0),这对我来说已经足够了。如果您的应用需要更优雅地终止(例如与后端协调或其他),请改为这样做。
    【解决方案2】:

    这是我的解决方案。一些细节:

    • 我在应用的home小部件中添加了Navigator,因此可以通过GlobalKey访问MaterialApp之外的导航器;
    • GestureDetector 行为设置为 HitTestBehavior.translucent 以将点击传播到其他小部件;
    • 您不需要Timer.periodic 来实现此目的。周期性定时器用于重复执行回调(例如,每 10 秒);
    • 设置小部件初始化和点击发生时的计时器。任何后续点击都将取消旧计时器并创建一个新计时器。调用_logOutUser 回调后,定时器被取消(如果有的话),每条路由都被弹出并推送新路由。
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      final _navigatorKey = GlobalKey<NavigatorState>();
      Timer _timer;
    
      @override
      void initState() {
        super.initState();
        _initializeTimer();
      }
    
      void _initializeTimer() {
        if (_timer != null) {
          _timer.cancel();
        }
    
        _timer = Timer(const Duration(seconds: 3), _logOutUser);
      }
    
      void _logOutUser() {
        _timer?.cancel();
        _timer = null;
    
        // Popping all routes and pushing welcome screen
        _navigatorKey.currentState.pushNamedAndRemoveUntil('welcome', (_) => false);
      }
    
      void _handleUserInteraction([_]) {
        _initializeTimer();
      }
    
      @override
      Widget build(BuildContext context) {
        return GestureDetector(
          behavior: HitTestBehavior.translucent,
          onTap: _handleUserInteraction,
          onPanDown: _handleUserInteraction,
          child: MaterialApp(
            title: 'Flutter Demo',
            theme: ThemeData(
              primarySwatch: Colors.blue,
            ),
            home: Navigator(
              initialRoute: 'welcome',
              key: _navigatorKey,
              onGenerateRoute: (settings) {
                return MaterialPageRoute(
                  builder: (context) {
                    return Scaffold(
                      appBar: AppBar(),
                      body: SafeArea(
                        child: Text(settings.name)
                      ),
                      floatingActionButton: FloatingActionButton(
                        onPressed: () => Navigator.of(context).pushNamed('test'),
                      ),
                    );
                  }
                );
              },
            ),
          ),
        );
      }
    }
    

    【讨论】:

      【解决方案3】:

      对于想严格回答标题问题(“如何在Flutter中一段时间​​不活动后执行函数”)的人,这是完整的解决方案:

      • 将您的 MaterialApp 包装在 GestureDetector 中,这样您就可以检测水龙头和平移。
      • 在 GestureDetector 中设置以下属性以避免与标准手势系统混淆:
        • 行为:HitTestBehavior.translucent
      • 在 GestureDetector 中设置回调以在点击/平移活动发生时重新启动计时器:
        • onTap: (_) => _initializeTimer()
        • onPanDown: (_) => _initializeTimer()
        • onPanUpdate: (_) => _initializeTimer()
      • 我们不应该忘记打字用户也是活跃用户,所以我们还需要在小部件的每个 TextField 中设置回调:
        • onChanged: (_) => _initializeTimer()
      • Timer _timer; 添加到您的 class_SomethingState。
      • 最后,在 initState() 处初始化 _timer,写入 _initializeTimer() 并写入 _handleInactivity(),包括当发生足够多的不活动时所需的操作:
      @覆盖 无效初始化状态(){ super.initState(); _initializeTimer(); } // 启动/重启定时器 无效_initializeTimer(){ 如果(_timer!= null){ _timer.cancel(); } // 5分钟后设置动作 _timer = Timer(const Duration(minutes: 5), () => _handleInactivity()); } 无效_handleInactivity(){ _timer?.cancel(); _timer = null; // TODO: 在此处输入您想要的代码 }

      【讨论】:

        【解决方案4】:

        你在正确的轨道上。您需要将整个应用程序包装在 GestureDetector 中。

        您可以将behavior 添加到GestureDetector。期望的行为应该是HitTestBehavior.translucent。这将允许您接收触摸事件,同时允许应用中的小部件也接收事件。

        然后您将需要一个计时器,每次点击都会重置该计时器。一旦超时,将用户导航到所需的屏幕。

        这是一个高级示例

        Timer timer;
        
        @override
          Widget build(BuildContext context)
          {
            return GestureDetector(
              child: Scaffold(
                appBar: AppBar(
                  title: Text("title"),
                ),
                body: Container(),
              ),
              behavior: HitTestBehavior.translucent,
              onTapDown: (tapDown)
              {
                if (timer != null)
                {
                  timer.cancel();
                }
                timer = Timer(Duration(minutes: 5), timeOutCallBack);
              },
            );
        }
        
          void timeOutCallBack() {
            Navigator.push(context, MaterialPageRoute(builder: (context) => DesiredScreen),);
          }
        
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-10-11
          • 1970-01-01
          • 1970-01-01
          • 2020-09-13
          • 2011-12-26
          • 1970-01-01
          • 2021-03-19
          • 1970-01-01
          相关资源
          最近更新 更多