The objective of my app is when I tap on start
button, a timer should start which will run a function every 5 seconds, and when stopped
the timer should cancel and stop that function from running. (Now I have different screens for the same purpose so I made a common screen and now I'm sending a Timer
timer to that common stateful class constructor). So, the problem is, the timer doesn't cancel, when I tap start
the function does executes every 5 seconds but doesn't stop.
Below I have provided some relevant code snippets to my query.
Main screen:
class StrategyOneScreen extends StatefulWidget {
const StrategyOneScreen({Key? key}) : super(key: key);
@override
State<StrategyOneScreen> createState() => _StrategyOneScreenState();
}
class _StrategyOneScreenState extends State<StrategyOneScreen> {
@override
Widget build(BuildContext context) {
Timer timer1 = Timer(Duration(), () {});
return StrategyScreen(
stratName: 'Free Strategy',
timer: timer1,
toggle: Provider.of<StrategyProvider>(
context,
listen: true,
).companyStratOneToggle,
toggleTimer: Provider.of<StrategyProvider>(
context,
listen: false,
).toggleCompanyTimer,
setToggle: Provider.of<StrategyProvider>(
context,
listen: false,
).setCompanyStratOneToggle,
chartLiveData: Provider.of<StrategyProvider>(
context,
listen: true,
).companyChartLiveData,
);
}
}
Inside the common StrategyScreen
:
class StrategyScreen extends StatefulWidget {
const StrategyScreen({
Key? key,
required this.stratName,
required this.timer,
required this.toggle,
required this.toggleTimer,
required this.setToggle,
required this.chartLiveData,
}) : super(key: key);
final stratName;
final timer;
final toggle;
final toggleTimer;
final setToggle;
final chartLiveData;
@override
_StrategyScreenState createState() => _StrategyScreenState();
}
class _StrategyScreenState extends State<StrategyScreen> {
@override
Widget build(BuildContext context) {
print("Timer: ${widget.timer}"); // console logs:=> Timer: null
return Scaffold(
...
Row(
children: [
Expanded(
child: Center(
child: FloatingActionButton.extended(
heroTag: 'btn1',
onPressed: widget.toggle == false
? () => {
widget.toggleTimer(
ref,
showNotification('title', 'body'),
widget.timer,
widget.toggle,
),
widget.setToggle(),
}
: null,
label: Text('Start'),
icon: Icon(Icons.launch),
backgroundColor: Colors.greenAccent,
),
),
),
Expanded(
child: Center(
child: FloatingActionButton.extended(
heroTag: 'btn2',
onPressed: widget.toggle
? () => {
widget.toggleTimer(
ref,
showNotification('title', 'body'),
widget.timer,
widget.toggle,
),
widget.setToggle(),
}
: null,
label: Text('Stop'),
icon: Icon(Icons.stop),
backgroundColor: Colors.pink,
),
),
),
],
),
StrategyProvider.dart
:
class StrategyProvider with ChangeNotifier {
// Toggles
bool _companyStratOneToggle = false;
bool get companyStratOneToggle => _companyStratOneToggle;
ChartLiveData _companyChartLiveData = ChartLiveData(
...
);
ChartLiveData get companyChartLiveData => _companyChartLiveData;
toggleCompanyTimer(ref, showNotification, timer, bool toggle) {
if (toggle == false) {
timer = Timer.periodic(
Duration(seconds: 5),
(Timer t) => {
fetchCompanyLiveStockData( // The function I want to run every 5 seconds
ref,
showNotification,
),
},
);
} else {
timer.cancel();
print("Timer Canceled!");
}
}
// Toggle setters for different strategies
setCompanyStratOneToggle() {
_companyStratOneToggle = !_companyStratOneToggle;
notifyListeners();
}
}
So as I told earlier that I am able to start the timer but cannot cancel it (as it is null
) and it keeps on running every 5 seconds. Below is the console output:
Unhandled Exception: NoSuchMethodError: Class 'Future<dynamic>' has no instance method 'call'.
E/flutter ( 2746): Receiver: Instance of 'Future<dynamic>'
E/flutter ( 2746): <asynchronous suspension>
E/flutter ( 2746):
I/flutter ( 2746): Timer: null
And when I press cancel
button:
The following NoSuchMethodError was thrown while handling a gesture:
The method 'cancel' was called on null.
Receiver: null
Tried calling: cancel()
When the exception was thrown, this was the stack
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:63:5)
#1 StrategyProvider.toggleSbinTimer
package:myapp/…/global/strategy_provider.dart:67
#2 _StrategyScreenState.build.<anonymous closure>
package:myapp/…/global/strategy_screen.dart:152
CodePudding user response:
Ultimately your issue is that you're trying to construct a Timer
object in toggleCompanyTimer
and trying to assign a reference to that object in the caller. However, Dart is not pass-by-reference, so there is no direct way for toggleCompanyTimer
to do that.
You'd be much better off if your StrategyProvider
class owned and managed the Timer
object entirely by itself. For example:
class StrategyProvider with ChangeNotifier {
Timer? timer;
// ... Other code...
void toggleCompanyTimer(ref, showNotification, bool stopTimer) {
// Cancel any existing Timer before creating a new one.
timer?.cancel();
timer = null;
if (!stopTimer) {
timer = Timer.periodic(
Duration(seconds: 5),
(Timer t) => {
fetchCompanyLiveStockData(
ref,
showNotification,
),
},
);
} else {
print("Timer Canceled!");
}
}
}