I have a CustomButton like so:
class CustomButton extends StatelessWidget {
const CustomButton(
{Key key, this.onPressed, this.child, this.padding})
: super(key: key);
final VoidCallback onPressed;
final Widget child;
final EdgeInsetsGeometry padding;
#override
Widget build(BuildContext context) {
final defaultPadding = EdgeInsets.symmetric(vertical: 13);
return FlatButton(
padding: padding ?? defaultPadding,
child: child,
onPressed: onPressed,
// + some irrelevant custom properties
);
}
}
I've included various applications of the widget, along with the desired outcome:
CustomButton(child: Text('👋'), onPressed: () {}) // 1⃣ no padding specified; use default
CustomButton(..., padding: null) // 2⃣ use NO padding
CustomButton(..., padding: EdgeInsets.only(left:1)) // 3⃣ use specified padding
The issue is that I can't differentiate between when padding is not passed 1⃣ and when padding: null is passed 2⃣ . In both cases, defaultPadding is applied -- whereas I want padding: null to result in no padding being applied.
How can I do this?
In your case, why not just let the default value for padding be your desired default? EdgeInsets.symmetric is a const constructor, so it can be used to create a default argument:
class CustomButton extends StatelessWidget {
static const defaultPadding = EdgeInsets.symmetric(vertical: 13);
const CustomButton(
{Key key, this.onPressed, this.child, this.padding = defaultPadding})
: super(key: key);
Normally you should use default arguments when you can. You'd use ?? for defaults when you can't (because the default you want isn't const) or when you do want null and an omitted argument to be handled in the same way.
In general, there is no built-way to distinguish between a default value being explicitly passed and a default value being used because it was omitted. In some cases you maybe could create a sentinel value that would be impossible for callers outside your library to pass:
class Foo {
const Foo(this.x);
final int x;
}
// Private so that nothing outside the library can pass it.
const _defaultFoo = Foo(0);
void bar({Foo foo = _defaultFoo}) {
if (foo == _defaultFoo) {
// Argument omitted.
} else {
// Argument was explicitly supplied.
}
}
final EdgeInsetsGeometry padding;
The padding doesn't have any initial value, thus whether you pass it null or nothing it will be null anyway.
you could fix this by changing your class constructor to this :
const CustomButton({Key key, this.onPressed, this.child, this.padding = EdgeInsets.zero})
this way the padding will have zero value instead of null if it is not given any value when it's being instantiated.
This will do what you want but vice versa, when it's passed null it will get default padding, if not passed anything, zero padding.
Hope it helps.
See the following example
class A {
final int a;
const A({this.a = 10});
}
void callMe({A par1 = const A(a: 10)}) {
if(par1 != null)
{
print(par1.a);
}
else {
print("null");
}
}
void main() {
callMe(par1: null);
callMe();
callMe(par1: A(a: 100));
}
So in your case you can do this simply tweak your constructor.
const CustomButton(
{Key key, this.onPressed, this.child, this.padding = EdgeInsets.symmetric(vertical: 13)})
: super(key: key);
Related
I am using Firebase remote config to store my color values. This gives me the flexibilty to update colors without the need to update my app. Now I have written myself a helper function which returns the color object.
In my Firebase remote config I have stored the hex color codes as strings. However, now I am facing the problem that my colors are no constants (const). This is a huge problem for me as I have set default color values in some constructors like here:
const CustomIcon(
{required this.iconType,
this.size,
this.color = Helper.getColor("black"),
Key? key})
: super(key: key);
Because my color is not a const value anymore I get the following error: https://dart.dev/tools/diagnostic-messages#non_constant_default_value
This is my helper function:
static Color getColor(colorName) {
final remoteConfig = FirebaseRemoteConfig.instance;
String colorString = remoteConfig.getString(colorName);
const color = Color(int.parse(colorString));
return color;
}
Do you have any idea on how I can solve this problem?
Kind regards
You sadly won't be able to const anything from the API. The const keyword implies that the Dart Analyzer knows what the value will be even before compiling. This isn't the case here, as the values come from the API.
However, you can still have a solution, by using a local Color default value, and checking for a null color.
class CustomIcon extends StatelessWidget {
final String iconType;
final int? size;
final Color? color;
late final Color defaultColor = Helper.getColor("black");
CustomIcon({required this.iconType, this.size, this.color, Key? key})
: super(key: key);
#override
Widget build(BuildContext context) {
final _color = color ?? defaultColor;
// Build your Widget
return Container(
color: _color,
height: 50,
width: 50,
);
}
}
Here is a simple DartPad showing it in action: https://dartpad.dev/?id=562c943972abaefd29b7b264e16ad5aa
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,
);
}
}
I'm creating a widget where I want to set the the size of the icons used in the sub-widgets globally.
class ItemsContainer extends StatefulWidget {
final List<Item> items;
final double iconSize; //global default
const ItemsContainer({
#required this.items,
this.iconSize = 56.0,
});
}
class Item {
final Icon icon;
const Item ({
#required this.icon,
});
}
What I'd like to do is this:
for (var item in items) {
if (item.size == null)
item.size = iconSize;
}
The problem I face is, that I can't set the size due to the fact, that I have a const constructor.
I could clone an existing Icon and change the original size, but is there a better way to do it?
Icon _getSizedIcon(Icon icon, double size) {
return icon.size != null ? icon :
Icon(icon.icon,
size: size,
color: icon.color,
key: icon.key,
semanticLabel: icon.semanticLabel,
textDirection: icon.textDirection,
);
}
The IconTheme widget is what you probably want:
https://api.flutter.dev/flutter/widgets/IconTheme-class.html
I try to create some custom widgets with some parameters in the constructor. This widget has some optional and required parameters.
how can make Function type parameter optional in my Widget.
class TextInputWithIcon extends StatefulWidget {
final String iconPath;
final String placeHolder;
final Function(bool) onFocusChange;
const TextInputWithIcon(
{Key key,
#required this.iconPath,
this.placeHolder = "",
this.onFocusChange})
: super(key: key);
#override
_TextInputWithIconState createState() => _TextInputWithIconState();
}
class _TextInputWithIconState extends State<TextInputWithIcon> {
#override
Widget build(BuildContext context) {
return MY_WIDGET;
}
}
Optional parameters can be either positional or named, but not both.
Named parameters are optional by default so you don't have to assign the default value.
If a parameter is optional but can’t be null, provide a default value.
With null safety
class TextInputWithIcon extends StatefulWidget {
final String iconPath;
final String placeHolder;
final Function(bool)? onFocusChange; // nullable and optional
const TextInputWithIcon(
{Key? key,
required this.iconPath, // non-nullable and required
this.placeHolder = "", // non-nullable but optional with a default value
this.onFocusChange, // nullable and optional
})
: super(key: key);
#override
_TextInputWithIconState createState() => _TextInputWithIconState();
}
Without null safety
const TextInputWithIcon(
{Key key,
#required this.iconPath,
this.placeHolder = "",
this.onFocusChange
})
: super(key: key);
Usage:
void _focusChanged(bool value) {
// using null-aware operator (for both with and without null safety)
onFocusChange?.call(value);
// or without null-aware operator
// with null safety
if(onFocusChange != null) {
onFocusChange!(value);
}
// without null safety
if(onFocusChange != null) {
onFocusChange(value);
}
}
Dart 2.17 update:
Although it often makes sense to place positional arguments first, named arguments can be placed anywhere in the argument list when it suits your API:
repeat(times: 2, () {
...
});
Have a look at Optional Parameters to understand better.
Edit: Thank you Jonah Williams to clarification.
You can use a default value that does nothing:
class TextInputWithIcon extends StatefulWidget {
final String iconPath;
final String placeHolder;
final Function(bool) onFocusChange;
const TextInputWithIcon(
{Key key,
#required this.iconPath,
this.placeHolder = "",
this.onFocusChange = _dummyOnFocusChange})
: assert(onFocusChange != null), super(key: key);
#override
_TextInputWithIconState createState() => _TextInputWithIconState();
static dynamic _dummyOnFocusChange(bool val) {}
}
I created a static named function instead of just a closure as a default value because closures are not const and currently default values need to be const.
I added the assert(...) to ensure that an error is shown when null is passed explicitly.
Another option if you don't like named parameters (like me :/) is:
function_name (argument1, [argument2]) {
// statements
}
arguments in brackets are optional.
source
I am trying to modify the cards_demo.dart found in Flutter examples. My purpose is that instead of having the built-in two cards' height be fixed as:
static final double height=300.0 (or some mandatory and fixed number), I want to have different height for the two cards.
So I modified the TravelDestination class to include a property height:
class TravelDestination {
const TravelDestination({ this.assetName, this.title, this.description, this.height });
final String assetName;
final String title;
final List<String> description;
final double height;
bool get isValid => assetName != null && title != null && description?.length == 3;
}
Then, in class TravelDestinationItem build function:
class TravelDestinationItem extends StatelessWidget {
TravelDestinationItem({ Key key, #required this.destination }) : super(key: key) {
assert(destination != null && destination.isValid);
}
static final double height = 512.0;
final TravelDestination destination;
#override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final TextStyle titleStyle = theme.textTheme.headline.copyWith(color: Colors.white);
final TextStyle descriptionStyle = theme.textTheme.subhead;
return new Container(
padding: const EdgeInsets.all(8.0),
height: destination.height,
//height: height,
child: new Card(
child: new Column(... ...
I assigned different height property to the two cards but the result is not working: they are still the same height as specified by static final double height.
If I comment out static final double height line, the compiler will remind me: No static getter 'height' declared...
I am very much confused on this behavior.
Can anyone help?
Since you're using items of varying height, you should remove this line from the call to the ListView constructor:
itemExtent: TravelDestinationItem.height,
Also, you'll need to hot-restart the app (hot-reloading won't update the destinations list with the new data, since it's a global variable).