Flutter variable loses value - dart

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.

Related

Dart. Late initialize final variables

Is there way to late initialize for final variables. The problem is many values initialized with entry point to the class, which is not constructor. Hence they cannot be final right now. But in scope of particular class they will not be changed. For ex.
Controller controller;
double width;
void setup(final itemWidth) {
controller = MyController();
width = itemWidth;
}
Could it be possible? Right now I see only solution as a annotation. You might think it's for visual effect. But in fact it helps to avoid unpredictable flow during testing.
It is now possible to late initialize variables. For more information see Dart's documentation. The text below is copied from Dart's documentation:
Late final variables
You can also combine late with final:
// Using null safety:
class Coffee {
late final String _temperature;
void heat() { _temperature = 'hot'; }
void chill() { _temperature = 'iced'; }
String serve() => _temperature + ' coffee';
}
Unlike normal final fields, you do not have to initialize the field in its declaration or in the constructor initialization list. You can assign to it later at runtime. But you can only assign to it once, and that fact is checked at runtime. If you try to assign to it more than once — like calling both heat() and chill() here — the second assignment throws an exception. This is a great way to model state that gets initialized eventually and is immutable afterwards.

Using ancestorInheritedElementForWidgetOfExactType in 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;

Flutter - BLoC pattern - How to use streams to invoke a method of another widget, i.e. an animation?

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();
}
}

Why callback a called immediately when passed to Widget [duplicate]

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(...)

Flutter: Timer Issue During Testing

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) {
...
}
}

Resources