What is the best way to reuse a widget in Flutter? - flutter

In my app, I need to reuse a same divider more than 20 times.
Which way should I follow for best memory performance?
Way 1:
class DividerX extends StatelessWidget {
const DividerX({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Divider(color: Colors.green, height: 22);
}
}
Way 2:
class DividerX {
const DividerX._();
static const Widget divider = Divider(color: Colors.green, height: 22);
}

The second one. From Dart's website:
Const means that the object's entire deep state can be determined entirely at compile time and that the object will be frozen and completely immutable. (...) [Const objects] are canonicalized. This is sort of like string interning: for any given const value, a single const object will be created and re-used no matter how many times the const expression(s) are evaluated.

Related

How to make a list of stateful widgets without passing inputs inside of the list?

I want to be able to randomly select certain widgets and use them as cards and then input values into their parameters. For example, if Ralph wanted three different fish and knew that he wanted to name them Jack, Piggy, and Simon, but his parents were buying and choosing the types of fish for him, how could we make a list of different fish at the store without names?
class Fish extends StatefulWidget {
const Fish ({
super.key,
this.color,
this.child,
this.name,
});
final Color color;
final Widget? child;
final String name;
#override
State<Fish> createState() => _FishState();
}
class _FishState extends State<Fish> {
String name = widget.name;
double _size = 1.0;
void grow() {
setState(() { _size += 0.1; });
}
#override
Widget build(BuildContext context) {
return Container(
color: widget.color,
transform: Matrix4.diagonal3Values(_size, _size, 1.0),
child: widget.child,
);
}
}
If I try to make a list of fish without naming them, it won't work since it needs me to input a name parameter. How can I avoid this or change the names afterward?
I would love to do something like this:
List<Widget> fishAtTheStore = [
Fish(color: Colors.red, child: Text("This is a fish")),
Fish(color: Colors.blue, child: Text("This is a fish")),
Fish(color: Colors.yellow, child: Text("This is a fish")),
Fish(color: Colors.green, child: Text("This is a fish")),
Fish(color: Colors.orange, child: Text("This is a fish")),
]
class RalphsAquarium extends StatefulWidget {
const RalphsAquarium({super.key});
#override
State<RalphsAquarium> createState() => _RalphsAquariumState();
}
class _RalphsAquariumState extends State<RalphsAquarium> {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
fishAtTheStore[0](name: "Jack"),
fishAtTheStore[3](name: "Piggy"),
fishAtTheStore[1](name: "Simon"),
],
);
}
}
The actual functionality outside of the aforementioned issues and the required parameters does not matter to me.
Looks like you need some State Management here.
You've got a lot of libraries available to achieve this : Provider (which I recommend), Riverpod, Bloc are the most common ones (please avoid GetX)
Once you pick your State Management library, the logic to implement is the following :
Create a class Fish (not a widget, a model) which will hold all the params of your Fish.
class Fish {
Fish(this.name);
final String name;
}
Use this class in your Widget allowing to pick fishes
Create a "controller" which job will be to keep in memory the fish which will be picked
In this controller, you can add all your logic (like creating methods allowing you to update the name of the fish)
I strongly advise you to read this article of the flutter documentation first, to fully understand how to implement what you need

Is there a way to specify function parameters and return value as const in dart?

I wrote an extension function to add SizedBox between every child in Column or Row to add space between child, instead of putting SizedBox between every child, for which I didn't find any other way around.
Column(
children: [
// widgets
].setSpace(height: 10),
)
Row(
children: [
// widgets
].setSpace(width: 10),
)
So here List<Widget> setSpace({double? height, double? width}) takes height or width for the SizedBox used between child. But since height and width are not const I cannot use const SizedBox. So is there any way in dart to say that both the parameters and the return type will ALWAYS be cosnt? like const List<Widget> setSpace({const double? height, const double? width}) like C/C++?
I don't think that's possible, mostly because const can be applied only on constructors and fields, not on generic functions.
Maybe you can achieve that by creating your own widget that adds the SizedBox in its build method, and create a const constructor.
EDIT: here's a piece of code of mine of a custom widget with a const constructor.
class UnlockPage extends StatefulWidget {
final String pcData;
const UnlockPage({Key? key, required this.pcData}) : super(key: key);
#override
Widget build(BuildContext context) {
[...]
}
}
EDIT 2: here's a piece of code tested in DartPad. I don't think it can get better than this.
class SpacedColumn extends StatelessWidget {
final double height;
final List<Widget> children;
const SpacedColumn({required this.height, required this.children});
#override
Widget build(BuildContext context) {
var actualChildren = <Widget>[];
for (var child in children) {
actualChildren.add(child);
actualChildren.add(SizedBox(height: height));
}
return Column(
children: actualChildren,
);
}
}
You can't. As you pass a value this one can be different from one call to others.
Notice that const as not the same signification on Flutter than on other languages.
With Flutter it indicates to the rendering engine that the widget or the method is always the same and that the rendering engine is not obliged to rebuild this Widget when rebuilding the screen.
The keyword that act as const in other languages is final
In Dart language const doesn't mean the same as in other languages. You should use final if you don't want to change the values later.

how to draw a line with two point in it in flutter

I have two dropdown button that represt time (0-23) and the user can choose a time range for the light to be on. I want to represent the choosen times by a line like below where the the red circles move when they choose different time. it does not need to be circle, anything just to represent the range of time.
anyone have an idea or comments on that?
Use RangeSlider
One option is to use the RangeSlider in Flutter, to select an range of two values.
One caveat of the solution is that the range values are only visible while moving the thumb-selectors. Albeit you could use the callback function in the example to show the selected range elsewhere.
Example:
import 'package:flutter/material.dart';
// This StatefulWidget uses the Material RangeSlider and provides a
// callback for the updated range.
class SliderExample extends StatefulWidget {
const SliderExample({
Key? key,
required this.onRangeSelected,
required this.values
}) : super(key: key);
final RangeValues values;
final Function(RangeValues) onRangeSelected;
#override
State<SliderExample> createState() => _SliderExampleState();
}
class _SliderExampleState extends State<SliderExample> {
late RangeValues selectedRange;
#override
void initState() {
super.initState();
selectedRange = widget.values;
}
#override
Widget build(BuildContext context) {
return RangeSlider(
values: selectedRange,
onChanged: (range) {
setState(() {
selectedRange = range;
});
widget.onRangeSelected(range);
},
min: widget.values.start,
max: widget.values.end,
divisions: 24,
labels: RangeLabels("${selectedRange.start.round()}", "${selectedRange.end.round()}"),
);
}
}
// Using the SliderExample widget we just defined:
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SliderExample(
values: const RangeValues(0, 23),
onRangeSelected: (range) => print("${range.start} - ${range.end}"),
),
],
),
),
);
}
}
main() => runApp(const App());
Other approaches
Time Picker
However, you might want to consider using a time-picker instead if you want more fine-tuned control for the time: https://material.io/components/time-pickers/flutter#using-time-pickers
You can use RangeSlider Widget
https://api.flutter.dev/flutter/material/RangeSlider-class.html

Standardizing sizes across application, but still use `const`

I'd like to standard size across a flutter application to comfort to a 4 pt grid. Here's one example of how this could be done:
class Spacing {
const Spacing(double val) : points = val * 4;
final double points;
}
class PtPadding extends Padding {
PtPadding({Spacing padding, Widget child}) : super(padding: padding.points, child: child);
}
PtPadding(padding: Spacing(4), child: Text('Hello'));
// or just with regular old Padding
Padding(padding: Spacing(4).points, child: Text('Hello'));
This is great, but it seems I forgo the ability to const my specialized PtPadding forces developers to use Spacing. On the other hand, just using Spacing in a constructor and accessing the points, prevents any widget from being "const"able. So it seems like I have to take a performance hit if I want to implement this spacing in my system.
I could have a class with static const members that point to doubles, but then I'm restrained to the sizes available (ie I can only have so many static members) and I also don't get the benefits of type restrictions.
I'm wondering if anyone else has thoughts in how I might approach this.
For what it's worth, I understand why Spacing(4).points is not a const (methods inherently aren't consts), but not sure how to get around this.
The problem is, you are extending Padding. Widgets are not made to be extended. Instead, you should use composition.
class Spacing {
const Spacing(double val) : points = val * 4;
final double points;
}
class PtPadding extends StatelessWidget {
const PtPadding({
Key key,
#required this.padding,
this.child,
}) : super(key: key);
final Spacing padding;
final Widget child;
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(padding.points),
child: child,
);
}
}

Preserve widget state when temporarily removed from tree in Flutter

I'm trying to preserve the state of a widget, so that if I temporarily remove the stateful widget from the widget tree, and then re-add it later on, the widget will have the same state as it did before I removed it. Here's a simplified example I have:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool showCounterWidget = true;
#override
Widget build(BuildContext context) {
return Material(
child: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
showCounterWidget ? CounterButton(): Text("Other widget"),
SizedBox(height: 16,),
FlatButton(
child: Text("Toggle Widget"),
onPressed: (){
setState(() {
showCounterWidget = !showCounterWidget;
});
},
)
],
),
),
);
}
}
class CounterButton extends StatefulWidget {
#override
_CounterButtonState createState() => _CounterButtonState();
}
class _CounterButtonState extends State<CounterButton> {
int counter = 0;
#override
Widget build(BuildContext context) {
return MaterialButton(
color: Colors.orangeAccent,
child: Text(counter.toString()),
onPressed: () {
setState(() {
counter++;
});
},
);
}
}
Ideally, I would not want the state to reset, therefor the counter would not reset to 0, how would I preserve the state of my counter widget?
The reason why the widget loose its state when removed from the tree temporarily is, as Joshua stated, because it loose its Element/State.
Now you may ask:
Can't I cache the Element/State so that next time the widget is inserted, it reuse the previous one instead of creating them anew?
This is a valid idea, but no. You can't.
Flutter judges that as anti-pattern and will throw an exception in that situation.
What you should instead do is to keep the widget inside the widget tree, in a disabled state.
To achieve such thing, you can use widgets like:
IndexedStack
Visibility/Offstage
These widgets will allow you to keep a widget inside the widget tree (so that it keeps its state), but disable its rendering/animations/semantics.
As such, instead of:
Widget build(context) {
if (condition)
return Foo();
else
return Bar();
}
which would make Foo/Bar loose their state when switching between them
do:
IndexedStack(
index: condition ? 0 : 1, // switch between Foo and Bar based on condition
children: [
Foo(),
Bar(),
],
)
Using this code, then Foo/Bar will not loose their state when doing a back and forth between them.
Widgets are meant to store transient data of their own within their scope and lifetime.
Based on what you have provided, you are trying to re-create CounterButton child widget, by removing and adding it back to the widget tree.
In this case, the counter value that is under the CounterButton was not saved or not saving in the MyHomePage screen, the parent widget, without any reference to a view model or any state management within or at the top level.
A more technical overview how Flutter renders your widgets
Ever wonder what is the key if you try to create a constructor for a widget?
class CounterButton extends StatefulWidget {
const CounterButton({Key key}) : super(key: key);
#override
_CounterButtonState createState() => _CounterButtonState();
}
keys (key) are identifiers that are automatically being handled and used by the Flutter framework to differentiate the instances of widgets in the widget tree. Removing and adding the widget (CounterButton) in the widget tree resets the key assigned to it, therefore the data it holds, its state are also removed.
NOTE: No need to create constructors for the a Widget if it will only contain key as its parameter.
From the documentation:
Generally, a widget that is the only child of another widget does not need an explicit key.
Why does Flutter changes the key assigned to the CounterButton?
You are switching between CounterButton which is a StatefulWidget, and Text which is a StatelessWidget, reason why Flutter identifies the two objects completely different from each other.
You can always use Dart Devtools to inspect changes and toggle the behavior of your Flutter App.
Keep an eye on #3a4d2 at the end of the _CounterButtonState.
This is the widget tree structure after you have toggled the widgets. From CounterButton to the Text widget.
You can now see that the CounterButton ending with #31a53, different from the previous identifier because the two widgets are completely different.
What can you do?
I suggest that you save the data changed during runtime in the _MyHomePageState, and create a constructor in CounterButton with a callback function to update the values in the calling widget.
counter_button.dart
class CounterButton extends StatefulWidget {
final counterValue;
final VoidCallback onCountButtonPressed;
const CounterButton({Key key, this.counterValue, this.onCountButtonPressed})
: super(key: key);
#override
_CounterButtonState createState() => _CounterButtonState();
}
class _CounterButtonState extends State<CounterButton> {
#override
Widget build(BuildContext context) {
return MaterialButton(
color: Colors.orangeAccent,
child: Text(widget.counterValue.toString()),
onPressed: () => widget.onCountButtonPressed(),
);
}
}
Assuming you named your variable _counterValue in the _MyHomePageState, you can use it like this:
home_page.dart
_showCounterWidget
? CounterButton(
counterValue: _counterValue,
onCountButtonPressed: () {
setState(() {
_counterValue++;
});
})
: Text("Other widget"),
In addition, this solution will help you re-use CounterButton or other similar widgets in other parts of your app.
I've added the complete example in dartpad.dev.
Andrew and Matt gave a great talk how Flutter renders widgets under the hood:
https://www.youtube.com/watch?v=996ZgFRENMs
Further reading
https://medium.com/flutter-community/flutter-what-are-widgets-renderobjects-and-elements-630a57d05208
https://api.flutter.dev/flutter/widgets/Widget/key.html
The real solution to this problem is state management. There are several good solutions for this available as concepts and flutter packages. Personally I use the BLoC pattern regularly.
The reason for this is that widget state is meant to be used for UI state, not application state. UI state is mostly animations, text entry, or other state that does not persist.
The example in the question is application state as it is intended to persist longer than the live time of the widget.
There is a little Tutorial on creating a BLoC based counter which could be a good starting point.