Saltar al contenido

Cómo ejecutar una función después de un período de inactividad en Flutter

Luego de observar en varios repositorios y sitios al final hemos dado con la solución que te mostramos ahora.

Solución:

Aquí está mi solución. Algunos detalles:

  • yo añadí Navigator en casa widget de la aplicación, por lo que es posible acceder al navegador fuera de MaterialApp a través de GlobalKey;
  • GestureDetector el comportamiento se establece en HitTestBehavior.translucent para propagar toques a otros widgets;
  • no necesitas Timer.periodic para este propósito. El temporizador periódico se usa para ejecutar la devolución de llamada repetidamente (por ejemplo, cada 10 segundos);
  • El temporizador establece cuándo se inicializa el widget y cuándo ocurre cualquier toque. Cualquier toque posterior cancelará el temporizador antiguo y creará uno nuevo. Después _logOutUser se llama a la devolución de llamada, se cancela el temporizador (si lo hubiera), se abren todas las rutas y se envía una nueva ruta.
class MyApp extends StatefulWidget 
  @override
  _MyAppState createState() => _MyAppState();


class _MyAppState extends State 
  final _navigatorKey = GlobalKey();
  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'),
                  ),
                );
              
            );
          ,
        ),
      ),
    );
  

FWIW, experimenté con el uso de un GestureDetector como se sugirió, pero no funcionó como se esperaba. El problema era que recibía un flujo continuo de gestos cuando no había actividad. Esto incluye cuando probé el onTap más restrictivo: devolución de llamada.

Vi esto en modo de depuración en un emulador. No experimenté más para ver si los teléfonos reales manifiestan el mismo comportamiento, porque incluso si no lo hacen ahora, podrían hacerlo en el futuro: claramente no hay garantía de especificación de que un GestureDetector no recibir eventos espurios. Para algo relacionado con la seguridad, como un tiempo de espera de inactividad, eso no es aceptable.

Para mi caso de uso, decidí que estaba bien detectar cuándo la aplicación es invisible por más de un período de tiempo determinado. Mi razonamiento para mi uso esperado es que el peligro real es cuando la aplicación es invisible y se olvidan de que está ahí.

Establecer este tipo de tiempo de inactividad es bastante fácil. me arreglo para llamar startKeepAlive() en el momento en que la aplicación obtiene acceso a información confidencial (por ejemplo, después de ingresar una contraseña). Para mi uso, simplemente cerrar la aplicación después del tiempo de espera está bien; obviamente, uno podría ser más sofisticado, si es necesario. Anyhoo, aquí está el código relevante:

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


En producción, probablemente extenderé el tiempo de espera 🙂

Para las personas que quieren la respuesta estricta a la pregunta del título (“Cómo ejecutar una función después de un período de inactividad en Flutter”), esta es la solución completa:

  • Envuelva su MaterialApp dentro de un Detector de gestos para que pueda detectar grifos y sartenes.
  • En GestureDetector establezca la siguiente propiedad para evitar alterar el sistema de gestos estándar:
    • comportamiento: HitTestBehavior.translucent
  • En GestureDetector, configure las devoluciones de llamada para reiniciar el temporizador cuando ocurra la actividad de tocar/panear:
    • onTap: (_) => _initializeTimer()
    • onPanDown: (_) => _initializeTimer()
    • onPanUpdate: (_) => _initializeTimer()
  • No debemos olvidar que un usuario que escribe también es un usuario activo, por lo que también debemos configurar devoluciones de llamada en cada TextField del widget:
    • onChanged: (_) => _initializeTimer()
  • Agregar Temporizador _temporizador; a su clase_AlgoEstado.
  • Finalmente, inicie _timer en initState(), escriba _initializeTimer() y escriba _handleInactivity(), incluidas las acciones deseadas cuando ocurra suficiente inactividad:
    @override
    void initState() 
      super.initState();
      _initializeTimer();
    

    // start/restart timer
    void _initializeTimer() 
      if (_timer != null) 
        _timer.cancel();
      
      // setup action after 5 minutes
      _timer = Timer(const Duration(minutes: 5), () => _handleInactivity());
    

    void _handleInactivity() 
      _timer?.cancel();
      _timer = null;

      // TODO: type your desired code here
    

Aquí puedes ver las comentarios y valoraciones de los lectores

Si crees que te ha resultado de ayuda nuestro post, agradeceríamos que lo compartas con otros programadores así contrubuyes a extender esta información.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *