My objective:
To create a countdown timer Flutter app for Android. The timers I start, have to keep running even if the phone’s screen is locked or if the User switches to a different app.
What I found online:
This and this, which are complex.
A seemingly simpler solution:
It uses StatelessWidget and Provider, to seemingly create widgets that have state, because Provider notifies the widgets of changes. So it’s possible to add new timers dynamically, and the timer countdown does not stop even if the phone screen is locked or if the User switches to a different app. No additions required in AndroidManifest.xml
.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'dart:async';
class TimerProvider with ChangeNotifier {
List<int> _timers = [];
List<Timer?> _timerInstances = [];
List<int> get timers => _timers;
void addTimer(int duration) {_timers.add(duration); _startTimer(_timers.length - 1); notifyListeners();}
void decrementTimer(int index) {if (_timers[index] > 0) {_timers[index]--; notifyListeners();}}
void _startTimer(int index) {
if (_timerInstances.length <= index || _timerInstances[index] == null) {_timerInstances.add(null);
_timerInstances[index] = Timer.periodic(Duration(seconds: 1), (timer) {decrementTimer(index);
if (_timers[index] <= 0) {timer.cancel();_timerInstances[index] = null;}
});
}
}
}
class TimerScreen extends StatelessWidget {
final int index;
TimerScreen({required this.index});
@override
Widget build(BuildContext context) {
final timerProvider = Provider.of<TimerProvider>(context);
return Scaffold(appBar: AppBar(title: Text('Timer ${index + 1}')),
body: Center(child: Text('Time Remaining: ${timerProvider.timers[index]} seconds', style: TextStyle(fontSize: 30),),),
);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(create: (_) => TimerProvider(), child: MaterialApp(title: 'Countdown Timer', theme: ThemeData.dark(), home: HomeScreen(),),);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Countdown Timers')),
body: Consumer<TimerProvider>(
builder: (context, timerProvider, child) {
return ListView.builder(itemCount: timerProvider.timers.length,
itemBuilder: (context, index) {
return Card(child: ListTile(title: Text('Timer ${index + 1}'), subtitle: Text('Time Remaining: ${timerProvider.timers[index]} seconds'),
onTap: () {Navigator.push(context, MaterialPageRoute(builder: (context) => TimerScreen(index: index),),);},
),
);
},
);
},
),
floatingActionButton: FloatingActionButton(onPressed: () {Provider.of<TimerProvider>(context, listen: false).addTimer(60);}, child: Icon(Icons.add),),
);
}
}
void main() {runApp(MyApp());}
Dependencies added:
cupertino_icons: ^1.0.6
provider: ^6.1.2
My question:
Earlier, I used Stateful widgets and FlutterBackground.enableBackgroundExecution()
via import 'package:flutter_background/flutter_background.dart';
, but when the phone’s screen locked, the timer would pause. But if you run the code I’ve provided above, it actually works if the phone screen is locked and works if the User switches to another app, even without using flutter_background
.
I’m new to Flutter, so I would like to know if creating an app in such a manner using StatelessWidget is “good engineering” in the Flutter/Dart ecosystem? I plan to expand the functionality to be able to pause/play the timer at the timer screen and also have a separate screen where the timer duration can be edited. I will also be saving the timers to storage using Hive.
3