I have a class called A which is a Stateless class and I have a class called B which is a Stateful class
The build method of A class is as follows
#override
Widget build(BuildContext context) {
return BlocProvider(
bloc: DashboardListBloc(),
child: Scaffold(
body: SingleChildScrollView(
child: Column(
children: <Widget>[
SafeArea(child: _dashboardAppBar(context)),
SizedBox(
height: 10.0,
),
B() // this is class B
],
),
)),
);
I have declared my bloc object in B class
Suppose in body of class A, i wrap SingleChildScrollView with RefreshIndicator, so how in its refresh property
i am supposed to call the methods of Bloc class whose reference are defined in class B.
I thought of moving everything to class B and removing class A but
that causes another problem as i have to initialise Bloc in init method and as init is called before build,
bloc will always result in null as i will be using BlocProvider InheritedWidget in build method of class B
You should create one single bloc instance and provide it to both classes.
In other words your bloc should not be created in class B but instead you can create your bloc by wrapping any common parent of A and B (such as your App widget) with a BlocProvider and using the create method. Then in any child widget you can do BlocProvider.of<MyBloc>() or context.read<MyBloc>() and this way both classes A and B will share the same Bloc instance.
Related
I am a very newbie in Dart and have big trouble to understand the "shortcut" of Dart code.
One of them is the "(...) {...}".
Could you take a look at the attached screenshot and help me to understand what are the "(..)" in blue and red rectangles?
Thank you!
child: Switch(
value: isSwitched,
onChanged: (value) {
setState(() {
isSwitched = value;
print(isSwitched);
});
},
activeTrackColor: Colors.lightGreenAccent,
activeColor: Colors.green,
),
These functions are called anonymous functions.
The onChange function took a function as an argument. And run that function with an argument it already has.
void fn(value) {
// setState code
}
// which you can use as
onChanged: fn,
But declaring a new function just to use at that one place can be tiresome and inefficient. That's why anonymous functions are useful. So you can write the previous code with anonymous functions as
onChanged: () { // the same as function fn but with no name or declaration
// setState code
}
The same goes for setState function. But it takes a function with no arguments.
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.
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) {
...
}
}