69 lines
2.1 KiB
Dart
69 lines
2.1 KiB
Dart
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 few seconds. 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<TWidget extends StatefulWidget> on State<TWidget> {
|
|
|
|
/// Interval in which the api data of the whole app is refreshed.
|
|
static const Duration refreshInterval = Duration(seconds: 15);
|
|
|
|
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<void> RefreshData();
|
|
|
|
Future<void> _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;
|
|
}
|
|
}
|
|
}
|