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 deGlobalKey
; GestureDetector
el comportamiento se establece enHitTestBehavior.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.