I have a periodic timer in one of my StatelessWidget's. Without going too much into detail, here is a snippet of code producing a timer:
class AnniversaryHomePage extends StatelessWidget {
. . .
void _updateDisplayTime(StoreInheritedWidget inheritedWidget) {
String anniversaryString = inheritedWidget.prefs.getString('anniversaryDate');
inheritedWidget.store.dispatch(DateTime.parse(anniversaryString));
}
/// Widget Methods
#override
Widget build(BuildContext context) {
final inheritedWidget = StoreInheritedWidget.of(context);
new Timer.periodic(this.refreshRate, (Timer timer) => _updateDisplayTime(inheritedWidget));
. . .
}
When I try pumping my the application's starting point into flutter test, I get the following error message:
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following assertion was thrown running a test:
A periodic Timer is still running even after the widget tree was disposed.
'package:flutter_test/src/binding.dart': Failed assertion: line 668 pos 7:
'_fakeAsync.periodicTimerCount == 0'
The question is, am I using my Timer.periodic incorrectly? If not, how do I mitigate this error?
The issue is that creating a Timer creates a resource which must be disposed, and therefore your widget is actually Stateful and not stateless. Specifically, the build method may be called 60 times a second (or more if the platform is 120fps). Any less is just an optimization.
You have a very critical bug in your code - the build method is creating a new Timer every time it is called. And since your timers are never cancelled, you could have hundreds or potentially thousands of events dispatched to your store.
To avoid situations like this, the framework has a State class, with an initState and dispose lifecycle. The framework promises that if it rebuilds your widget, it won't call initState more than once and it will always call dispose. This allows you to create a Timer once and reuse it on subsequent calls to build.
For example, you can move most of your logic to the State like below. Leave the refreshRate on the widget, and you can even cancel and update your timer using the didUpdateWidget lifecycle too.
class AnniversaryHomePage extends StatefulWidget {
#override
State createState() => new AnniversaryHomePageState();
}
class AnniversaryHomePageState extends State<AnniversaryHomePage> {
Timer _timer;
#override
void initState() {
_timer = new Timer.periodic(widget.refreshRate,
(Timer timer) => _updateDisplayTime(inheritedWidget));
super.initState();
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
...
}
}
Related
I'm trying to write a fully reactive hello-world example in Flutter, specifically, a simple counter. Here, I have a null-stream from a "+1" button which gets triggered on button press:
class ReactiveButton extends StatelessWidget {
final _controller = StreamController();
Stream<void> pressedEvent;
final text;
ReactiveButton({this.text = ""}) {
pressedEvent = _controller.stream;
}
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () => _controller.sink.add(null),
child: Text(text),
);
}
}
I want to convert that stream into a sequence of natural numbers to feed into an RxText. So I use the zipWith method from rxdart with a tight stream of natural numbers, discarding the null value. Now the problem is that using the stream of natural numbers created by a tight while (true) loop results in an infinite cycle because (presumably) the stream never returns control between yields:
Stream<int> naturals() async* {
int count = 0;
while (true) {
yield count++;
}
}
Alternatively, I can use Stream.periodic constructor with a close to zero period. However, this results into my application gradually becoming sluggish (presumably, due to the streams indefinitely counting in vain).
So what I need is a lazy stream that only produces values when asked (immediately). That actually sounds like an Iterable but for Iterable I don't have a method to zip it with a stream. I thought about implementing a function which takes a list of streams/iterables and returns a stream of lists, working more or less like the regular zipper. It seems rather complicated though, and I'm not sure I can write a production-ready implementation.
The question is: what are my options? Is there a ready-to-go solution for this? Should I at all try to go full reactive with Flutter?
I have a chat object in a InheritedWidget above to root widget of my app. I want to access this object within initState to set the initial state of a child widget. I've the following code:
void initState() {
super.initState();
final inheritedWidget = context.ancestorInheritedElementForWidgetOfExactType(MyInheritedWidget).widget;
inheritedWidget.chat.someFunction();
}
I'm getting an error saying:
"The getter 'chat' isn't defined for the class 'InheritedWidget'
Am I using the method wrong, how do you use it?
You have to cast widget first:
final inheritedWidget = context.ancestorInheritedElementForWidgetOfExactType(MyInheritedWidget).widget as MyInheritedWidget;
Suppose there is a widget with a method controlling visibility animation, toggleVisibility(). In a BLoC pattern, I want to use a stream to invoke that function. I find this pretty tricky.
Since it is an animation rather than a complete redraw, a StreamBuilder doesn't fit.
Manually add a listener to the BLoC streams isn't convenient either.
In initeState() function of the target widget, we don't have a context, so it's hard to get a reference to the BLoC.
Edit: This is not the case after I read Rémi Rousselet's answer. We can access the context even outside of build() function, because State<T> class has a property named 'context' and is documented in Flutter's docs.... I wasn't aware of that.
In build(context) function of the target widget, we have the context. But the widget can be frequently re-built, so you have to manually clean the outdated subscriptions. Otherwise it will create tons of garbages.
A hack with StreamBuilder can do, since the StreamBuilder has implemented all the subscription and unsubscription functionalities. Insert a StreamBuilder somewhere in the layout of the target widget.
StreamBuilder(
stream: Bloc.of(context).stream,
builder: (context, snapshot){
toggleVisiblity();
return Container():
}
);
But this is really a hack. It mixed layout with logic and introduced a useless widget which could cause layout bugs.
So I wonder if there is a good way of doing this in flutter.
You cannot use StreamBuilder to do that. You have to manually listen to the stream
class Example extends StatefulWidget {
#override
ExampleState createState() => ExampleState();
}
class ExampleState extends State<Example> {
StreamSubscription subscription;
#override
void didChangeDependencies() {
super.didChangeDependencies();
Stream stream = Bloc.of(context).someStream;
subscription?.cancel();
subscription = stream.listen((value) {
// do something
});
}
#override
void dispose() {
subscription?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container();
}
}
I have a screen with a list of textfields and a dropdown option. I declared a variable in the StatefulWidget class and use it in the class through widget.variableName, however after entering some values on the textfields, I noticed the widgets get updated and my variable loses its value and I am left with a null variable. I have tried initializing it in initState() but it still loses it's value.
Example:
class Screen extends StatefulWidget{
var variable;
#override
_ScreenState createState(){
variable = "dummyText";
return _ScreenState();
}
}
class _ScreenState extends State<Screen>{
#override
Widget build(BuildContext context) {
return Scaffold(
body: _layout(),
);
}
_layout(){
print(" value of variable: ${widget.variable}");
//imagine lots of textfields here in a listview
}
}
After entering text on some textfields, the variable loses it's value and it resets to null. How can i maintain it's value or where should I even declare it to make it not lose it's value.
You can't set mutable variable inside the StatefulWidget subclass. All fields of StatefulWidget must be final or const.
Instead, move that variable inside the State subclass.
class Screen extends StatefulWidget {
_ScreenState createState() => _ScreenState();
}
class _ScreenState extends State<Screen> {
String variable;
#override
void initState() {
variable = "dummyText";
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _layout(),
);
}
_layout() {
print(" value of variable: $variable");
//imagine lots of textfields here in a listview
}
}
Any state (i.e. variables) should be held in the class that inherits State. The class that inherits StatefulWidget should actually only have final variables - if you look at the Dart Analysis it should actually show an error as it inherits an #immutable class.
It's hard to tell without a bit more context about what you're doing, but generally if a value is passed in to the object you want to store it as a final variable in the class that inherits StatefulWidget, and store the actual value in a State wherever the change is actually affected.
Flutter is optimized for building objects, so don't worry about the performance implications of instantiating objects many times.
Note that if your class is the right place for the variable to be held, but you also want an initial value, you could pass in the initial value to the StatefulWidget then retrieve the value into the State in the initState call.
I'd recommend reading the part of the flutter tutorial about stateless and stateful widgets to get a deeper understanding of how they work.
This question already has answers here:
Can't get button press to work in flutter
(4 answers)
Closed 4 years ago.
I have created a Custom Widget MyTimer in which I have passed a callback function which have to be called when it gets completed as shown in screenshot
Here is my MyTimer class code:
class MyTimer extends StatefulWidget {
VoidCallback callback;
MyTimer(this.callback);
#override
State<StatefulWidget> createState() => new MyTimerState();
}
But as page loads it is called automatically. How can I prevent this?
You need to pass a function reference, not the result of a function call
new MyTimer(() => gameOver())
Without () => gameOver() is executed and the result is passed to new MyTimer(...)