fix(flutter): tighten version-check cadence to 1m during active use
Drops the staleness gate from 1h to 1m and adds a Timer.periodic that fires recheckIfStale every minute while the app is foregrounded. Net effect: ~1 check per minute of active use, ~60 KB/hr data — trivially affordable for the value of faster recovery when min_client_version bumps server-side. Timer is paired with the lifecycle observer: started in initState + on resume, stopped in dispose + on pause/inactive/hidden/detached so backgrounded apps don't burn battery on probes the user can't see. Staleness gate still wraps the call so concurrent triggers (timer + resume firing close together) dedupe to one network call. Manual "Check now" still bypasses the gate via recheck(). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
@@ -50,7 +52,12 @@ String _stringFromResult(VersionResult r) {
|
||||
class VersionCheckController extends AsyncNotifier<VersionResult> {
|
||||
static const _kResult = 'version_check_result';
|
||||
static const _kAtMs = 'version_check_at_ms';
|
||||
static const _staleMs = 60 * 60 * 1000; // 1h
|
||||
// 1 minute. The /healthz response is sub-1KB and the call is
|
||||
// non-blocking, so a tight cadence buys faster recovery from
|
||||
// server-side min_client_version bumps without measurable cost. The
|
||||
// gate also acts as a safety net against duplicate calls when the
|
||||
// periodic timer and a resume event fire close together.
|
||||
static const _staleMs = 60 * 1000;
|
||||
|
||||
@override
|
||||
Future<VersionResult> build() async {
|
||||
@@ -151,6 +158,13 @@ class VersionGate extends ConsumerStatefulWidget {
|
||||
|
||||
class _VersionGateState extends ConsumerState<VersionGate>
|
||||
with WidgetsBindingObserver {
|
||||
// Polling interval during active foreground use. Paired with the
|
||||
// controller's 1m staleness gate so concurrent fires (timer + resume)
|
||||
// dedupe to a single network call. Tight cadence is affordable —
|
||||
// /healthz is sub-1KB and the call is non-blocking.
|
||||
static const _pollInterval = Duration(minutes: 1);
|
||||
Timer? _pollTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -158,20 +172,46 @@ class _VersionGateState extends ConsumerState<VersionGate>
|
||||
// hydrates from cache + fires a background check when stale.
|
||||
ref.read(versionCheckProvider);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_startPolling();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_stopPolling();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
// Staleness-gated: cheap when the user just briefly left the app.
|
||||
void _startPolling() {
|
||||
_pollTimer?.cancel();
|
||||
_pollTimer = Timer.periodic(_pollInterval, (_) {
|
||||
// ignore: unawaited_futures
|
||||
ref.read(versionCheckProvider.notifier).recheckIfStale();
|
||||
});
|
||||
}
|
||||
|
||||
void _stopPolling() {
|
||||
_pollTimer?.cancel();
|
||||
_pollTimer = null;
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
switch (state) {
|
||||
case AppLifecycleState.resumed:
|
||||
// Re-arm the timer + fire one immediate (staleness-gated) check
|
||||
// so users who foreground after a long away period get a fresh
|
||||
// result without waiting for the next tick.
|
||||
// ignore: unawaited_futures
|
||||
ref.read(versionCheckProvider.notifier).recheckIfStale();
|
||||
_startPolling();
|
||||
case AppLifecycleState.paused:
|
||||
case AppLifecycleState.inactive:
|
||||
case AppLifecycleState.hidden:
|
||||
case AppLifecycleState.detached:
|
||||
// Stop firing while backgrounded — no need to burn battery on
|
||||
// health probes the user can't see the result of.
|
||||
_stopPolling();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user