Refactoring question: Extract as widget/method/variable... What are pros and cons? - flutter

All three of them do basically the same thing. What are the best practices? In which case shall I use which extracting way? What is the standard?
I guess these could be some points that could matter:
readability
performance
number of lines
Example:
Extract the Container. Which way would you prefer and why?
class Example extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container();
}
}
1. Extract Container as Widget (+6 lines):
class Example extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ExtractedAsWidget();
}
}
class ExtractedAsWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container();
}
}
2. Extract Container as Method (+1 (in some cases 3) line):
class Example extends StatelessWidget {
#override
Widget build(BuildContext context) {
return buildContainer();
}
Container buildContainer() => Container();
}
3. Extract Container as Variable (+1 line):
class Example extends StatelessWidget {
#override
Widget build(BuildContext context) {
var container = Container();
return container;
}
}

Extracting as Widget is the best practice as the Flutter framework can optimize widgets rather than methods or variables. But for this particular case, I would extract Container as method. Extracting as Widget would be too overkill.
Extracting as Variable
Extracting as variable should only be done when your widget is fixed, being used only in a single file & doesn't need any parameters.
Extracting as Method
Extracting as method should be done when your widget is being used only in a single file (and it can also use some parameters for additional customization). It also increases code readability.
Extracting as Widget
Extracting as Widget is the most optimal way to create a widget that is being used multiple times in different files. Flutter recommends creating as much StatelessWidget as possible.
The most simple example that I can suggest is the use of buttons. Every app has a fixed primary color button that we use. So, instead of creating a variable or method, we should create a StatelessWidget of that button & use that in multiple screens of our app.

Related

Which is better when using provider instance in a new widget?

Let's say I've written my code as below.
I've got a provider called SampleProvider, and I'm using it in my main widget.
class SampleProvider extends ChangeNotifier {}
class MainWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
SampleProvider provider = Provider.of<SampleProvider>(context);
}
}
And then, I want to make a new widget and use this provider in the new widget.
There will be two choices.
First, I just instantiate another provider in the new widget as below.
class NewWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
SampleProvider provider = Provider.of<SampleProvider>(context);
}
}
Or, I can send it from the main widget to the new widget as a constructor parameter.
Like this:
class NewWidget extends StatelessWidget {
final SampleProvider provider;
NewWidget(this.provider);
#override
Widget build(BuildContext context) {
}
}
I guess the first option is better because flutter draws a widget based on its build context, but I'm not sure.
I've googled it quite long, but there was no success.
Can anybody tell me whether I am right or wrong? Or Do they have no difference?
Prefer the first solution, it's easier to refactor.
Suppose you need move NewWidget in your widget tree, you also need to modify the "paramter pass" code if you choose second solution, which is not necessary with first solution.
One of Provider pacakage's purpose is avoid passing parameter deep in the widget tree by the way.
Depend on preference not like first or second one.
Have an exception when obtaining Providers inside initState. What can I do?
This exception happens because you're trying to listen to a provider from a life-cycle that will never ever be called again.
It means that you either should use another life-cycle (build), or explicitly specify that you do not care about updates.
As such, instead of:
initState() {
super.initState();
print(context.watch<Foo>().value);
}
you can do:
Value value;
Widget build(BuildContext context) {
final value = context.watch<Foo>.value;
if (value != this.value) {
this.value = value;
print(value);
}
}
which will print value whenever it changes (and only when it changes).
Alternatively, you can do:
initState() {
super.initState();
print(context.read<Foo>().value);
}
SRC: https://github.com/rrousselGit/provider#i-have-an-exception-when-obtaining-providers-inside-initstate-what-can-i-do
Yes, I believe the first option is the better way, of the top of my head I can't think of any situation in which you would prefer the second option to the first.
If you don't use new widget as children of any other widget , first choice is better .
otherwise , second is better .

Flutter Widget building & structure

It seems there is a lots of way to build Widget in Flutter. It is a bit overwhelming to understand the right way of doing it. Can anyone explain me the difference between defining a variable inside/outside the build function as follow:
Inside class
class Login extends StatelessWidget {
TextFormField username() {
return TextFormField();
}
#override
Widget build(BuildContext context) {
return Scaffold();
}
}
Inside build function
class Login extends StatelessWidget {
#override
Widget build(BuildContext context) {
TextFormField username() {
return TextFormField();
}
return Scaffold();
}
And also is there is any difference between defining the variable as follow:
class Login extends StatelessWidget {
final username = TextFormField();
TextFormField username() {
return TextFormField();
}
#override
Widget build(BuildContext context) {
return Scaffold();
}
}
Thanks a lot for your help.
Jonathan.
There are several ways to do anything within the programming world. With Flutter / Dart it would be no different.
However, there are more "orthodox" ways of doing things, which will certainly save you from structural problems.
1- Declaration of an instance.
When you have a class that is not a widget, you usually use common instances of classes.
String name = "Jonny";
TextEditingController controller = TextEditingController();
When you declare widgets, there are 3 ways to do this, but only 1 is recommended.
The first is to store the widget in a variable (just like we did above).
final text = Text ('Pablo');
Build widget (BuildContext context) {
return Scaffold (body: text);
The second, and least recommended of all, is to create a method that gives you an instance of that widget
Widget text () {
return Text ('Pablo');
}
Build widget (BuildContext context) {
return Scaffold (body: text ());
This second approach is less recommended, because you will manufacture a new instance every time the method is called. If this widget has a heavy rendering, you are throwing resources in the trash.
The third (and highly recommended, I would tell you to stick to it and forget about it any other way) is nesting widgets in the tree:
Build widget (BuildContext context) {
return Scaffold (body: Text ('Pablo'));
This ensures that the same instance of Text () remains on the tree, and consequently if it were a much larger widget, this would be the most resource-efficient approach.
What if my tree becomes giant? how to avoid the ripple effect?
Simple, create new StatelessWidgets to break the tree's cascading effect.
Build widget (BuildContext context) {
return Scaffold (body: MyText());
class MyText extends StatelessWidget {
Build widget (BuildContext context) {
return Container (child:
Center (child: Text ('Pablo')),
);
}
}
Note: This was done for example purposes only, you must componentize reusable, or componentizable widgets, to avoid the effect:
aa
aaa
aaaa
aaaaa
aaaaaa
aaaaaaa
aaaaaaa
But if you have a few lines of code you don't have to worry about it as much.

Trying to access state from widget, probably using wrong design

I'm learning flutter and trying to make a kind of MutableImage widget. The idea is to make a MutableImage StatefulWidget that would rebuild when a new image is provided. I try to avoid rebuilding the whole widget tree each time the image is changed because that seems overkill, and I plan to update the image several times per second. So I want to rebuild only that MutableImage widget.
So here is the code I have, with comments to explain where I'm stuck :
class MutableImage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MutableImageState();
}
void updateImage(List<int> bytes) {
// !!!!!! Would like to call this method here, but state is not available from Widget, which means I want to do something wrong, but not sure exactly how I should do it...
// this.state.updateImage(bytes);
}
}
class MutableImageState extends State<MutableImage> {
List<int> _bytes;
void updateImage(List<int> bytes) {
setState(() {
_bytes=bytes;
});
}
#override
Widget build(BuildContext context) {
if ((_bytes==null)||(_bytes.length==0)) {
return Center(child: CircularProgressIndicator());
}
return Image.memory(_bytes);
}
}
Then the idea was to use this widget like this for example in another stateful widget
MutableImage _mutableImage;
#override
Widget build(BuildContext context) {
if (_mutableImage == null) _mutableImage=MutableImage();
return : Row( //using Row as an example, the idea is that the mutable Image is deep into a tree of widgets, and I want to rebuild only _mutableImage when image changes, not all widgets.
children : <Widget>[
child0, child1, _mutableImage, child3, child4
]
);
}
void updateImage(List<int> bytes) {
_mutableImage?.updateImage(bytes);
}
So is there a good way to do this ? I'm quite confused, thx for any help/hint.
This is a place for an application of a GlobalKey. In the parent Widget of MutableImage make a global key and pass that to MutableImage. With that key you can access MutableImage state by using .currentState on the key and calling updateImage.
You'll have to add key as an argument of the MutableImage constructor and call super(key: key). updateImage should also be moved the the state of MutableImage.
Key:
final GlobalKey<MutableImageState> _imageKey = GlobalKey<MutableImageState>();
Pass the key:
MutableImage(key: _imageKey);
Access the state:
_imageKey.currentState.updateImage();

Flutter: How can I avoid recreating the model for parts of the application?

My app uses a set of Card()s inside a PageView(), each card has multiple text lines.
The user gives input for each line (e.g. modifying the text etc.).
I want to keep this input for a while.
This is how my code looks at the moment, abstractly speaking:
class MyCard extends StatefulWidget {
final List<Widget> _myLines = [];
#override
State<StatefulWidget> createState() => MyCardState();
}
class MyCardState extends State<MyCard> {
...
#override
Widget build(BuildContext context) {
...
widget._myLines.add(ChangeNotifierProvider(
create: (context) => MyLineModel(context, lineText),
child: RecipeLine())
...
}
}
This doesn't work well:
As soon as I swipe left / right through the PageView onto other cards and then swipe back, the Card is being built again. This also leads to a rebuild of MyLineModel, which in turn erases all the user's input.
How can I avoid the rebuild of MyLineModel and keep the user's input?
You can solve this in 2 ways:
Create the model outside the widget and pass the model for the widget, as a variable to a constructor for example, or using Provider or any other technique of Dependency Injection.
Using any of the KeepAlive APIs, such as the AutomaticKeepAliveClientMixin as:
class MyCardState extends State<MyCard> with AutomaticKeepAliveClientMixin {
...
#override
bool get wantKeepAlive => true;
}
As the official documentation states, we can simply use the ChangeNotifierProvider.value() constructor...
In the original code:
class MyCardState extends State<MyCard> {
...
#override
Widget build(BuildContext context) {
...
widget._myLines.add(ChangeNotifierProvider.value(
value: MyLineModel(context, lineText),
child: RecipeLine())
...
}
}

Using Builder instead of StatelessWidget

Is there any objection against using
final foo = Builder(builder: (BuildContext context) {
...
});
instead of
class Foo extends StatelessWidget {
#override
Widget build(Build context) {
...
}
}
?
Using a Builder instead of making a proper subclass has flaws.
Flutter's widget system uses the runtimeType variable to determine how it should behave.
To be more specific, when runtimeType changes, Flutter will unmount the previous widget tree at that location, and then mount the new one.
But by using Builder the runtimeType never changes, and it confuses the framework.
This, in turn, means that by using Builder instead of making a StatelessWidget subclass, it could have undesired behaviors like reusing the state of the previous widget tree.
For the sake of an example, consider that we want to switch between two different kinds of widget – both of which instantiate a TextField.
With builders our two widgets would be:
final foo = Builder(builder: (_) => TextField());
final bar = Builder(builder: (_) => TextField());
then used this way:
Widget build(BuildContext context) {
return condition ? foo : bar;
}
With classes we'd have:
class Foo extends StatelessWidget {
#override
Widget build(Build context) => TextField();
}
class Bar extends StatelessWidget {
#override
Widget build(Build context) => TextField();
}
used as such:
Widget build(BuildContext context) {
return condition ? Foo() : Bar();
}
At this point, we run our application and starts typing some things inside the text field from our foo/Foo widget.
And then we're doing an action that will switch to bar/Bar.
In this step, the result will vary depending on if you used a class or a Builder:
using foo/bar, the text field inside bar shows the text you typed in the text field from foo.
using Foo/Bar classes, this does not happen. The text field inside Bar correctly resets to its initial value.