import 'dart:async'; import 'package:flutter/material.dart'; /// Keeps the data of a widget up to date by reloading it every /// [refreshInterval]. /// /// Mix this into the [State] of every widget that shows data of the backend and /// implement [RefreshData]. The mixin only owns the timer, fetching the data and /// putting it on screen stays in the widget itself. /// /// The backend has no push channel, so the only way to notice new agents, /// tasks or reports is polling. The interval is therefore a compromise between /// fresh data and load on the api. /// /// A refresh happens quietly: implementations are expected to keep the data /// that is already on screen while the request runs, so that the user neither /// sees a spinner nor loses the scroll position every half minute. Failures of /// a refresh are swallowed on purpose - the data on screen simply stays as it /// is and the next tick tries again. mixin AutoRefreshState on State { /// Interval in which the api data of the whole app is refreshed. static const Duration refreshInterval = Duration(seconds: 30); Timer? _refreshTimer; /// True while a refresh triggered by the timer is running. A slow api must /// not pile up requests. bool mIsAutoRefreshing = false; @override void initState() { super.initState(); _refreshTimer = Timer.periodic(refreshInterval, (_) => _onTick()); } @override void dispose() { _refreshTimer?.cancel(); _refreshTimer = null; super.dispose(); } /// Fetches the data of this widget again. Called every [refreshInterval] as /// long as the widget is mounted. /// /// Implementations may let api errors escape, [_onTick] catches them. Future RefreshData(); Future _onTick() async { if (mIsAutoRefreshing || !mounted) { return; } mIsAutoRefreshing = true; try { await RefreshData(); } catch (error) { // An automatic refresh must never replace data that is already on screen // with an error message, so a failed tick is ignored. } finally { mIsAutoRefreshing = false; } } }