The original experiment used a small subject/observer pair, then exposed the subject through Provider rather than a static global. Those choices address notification and access scope separately.
Subject and shared state
class Subject<T> {
List<Observer<T>> observers = <Observer<T>>[];
T _state;
T get state => _state;
set state(T value) {
_state = value;
notifyAllObservers();
}
void addObserver(Observer<T> observer) {
observers.add(observer);
}
void removeObserver(Observer<T> observer) {
observers.remove(observer);
}
void notifyAllObservers() {
for (final Observer<T> observer in observers) {
observer.update(state);
}
}
}
abstract class Observer<T> {
void update(T state);
}
import 'observable.dart';
class TimerProvider extends Subject<int> {
}
Subscribe from a widget
import 'package:flutter/material.dart';
import 'package:habitapp/services/app/observable.dart';
import 'package:habitapp/services/app/timer_provider.dart';
import 'package:provider/provider.dart';
class WObservable extends StatefulWidget {
const WObservable({Key key, this.child, this.update}) : super(key: key);
final Widget child;
final Function(int state) update;
@override
_WObservableState createState() => _WObservableState();
}
class _WObservableState extends State<WObservable>
with WidgetsBindingObserver, Observer<int> {
TimerProvider timerProvider;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
timerProvider = Provider.of(context, listen: false);
_attach();
});
}
@override
void dispose() {
_detach();
super.dispose();
}
@override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
print('state: $state');
switch (state) {
case AppLifecycleState.paused:
_detach();
break;
case AppLifecycleState.resumed:
_attach();
break;
default:
break;
}
}
@override
Widget build(BuildContext context) {
return widget.child ?? Container();
}
@override
void update(int state) {
if (widget.update != null) {
widget.update(state);
}
}
// Attach to timer provider
void _attach() {
if (timerProvider != null) {
timerProvider.addObserver(this);
update(timerProvider.state);
}
}
// Detach from timer provider
void _detach() {
timerProvider?.removeObserver(this);
}
}
Supply the subject through the application tree
Future<void> myMain() async {
/// Start services later
WidgetsFlutterBinding.ensureInitialized();
/// Run Application
runApp(
MultiProvider(
providers: <SingleChildWidget>[
Provider<TimerProvider>(create: (_) => TimerProvider()),
],
child: const MyApp(),
),
);
}
class MyApp extends StatefulWidget {
const MyApp({Key key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
Timer _timer;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
/// After first build
WidgetsBinding.instance.addPostFrameCallback((_) {
_startTimer();
});
}
@override
void dispose() {
_stopTimer();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
switch (state) {
case AppLifecycleState.paused:
_stopTimer();
break;
case AppLifecycleState.resumed:
_startTimer();
break;
default:
break;
}
}
@override
Widget build(BuildContext context) {
/// Build Material app
return MaterialApp(
debugShowCheckedModeBanner: false,
home: WObservable(),
);
}
/// Start timer
Future<void> _startTimer() async {
_stopTimer();
await _timerLogic();
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
_timerLogic();
});
}
// Timer logic
Future<void> _timerLogic() async {
context.read<TimerProvider>().state = DateTime.now().millisecondsSinceEpoch;
}
/// Stop timer
void _stopTimer() {
if (_timer != null) {
_timer.cancel();
_timer = null;
}
}
}
A provider makes access convenient, but does not automatically cancel timers or subscriptions. Duplicate observers, mutation during notification, and notifications after disposal require a policy in the complete implementation.
The example uses historical Dart/Flutter syntax and application imports. No timer or widget was executed for this revision. Source: Gist revision.