I have proxy object companyCustomColors:
class CustomColors {
final CompanyCustomColors companyCustomColors;
CustomColors(BuildContext context)
: companyCustomColors =
Theme.of(context).extension<CompanyCustomColors>() ?? defaultColors;
Color get vipColor => companyCustomColors.vipColor;
Color get linksColor => companyCustomColors.linksColor;
Color get linkPressedColor => companyCustomColors.linkPressedColor;
}
Is it possible to use some Dart features (proxy, mixin, delegate) to get rid of these getters (vipColor, linksColor, linkPressedColor), but still have IDE autocomplete suggestions for CustomColors?
This object used like this one:
Text('sample',
style: TextStyle(
color: CustomColors(context).vipColor,
height: lineHeight,
),
)
Other classes used in this example:
class CompanyCustomColors extends ThemeExtension<CompanyCustomColors> {
const CompanyCustomColors({
required this.vipColor,
required this.linksColor,
required this.linkPressedColor,
});
final Color vipColor;
final Color linksColor;
final Color linkPressedColor;
}
const CompanyCustomColors defaultColors = CompanyCustomColors(
vipColor: AppColors.orange,
linksColor: AppColors.blue,
linkPressedColor: AppColors.blue_pressed,
);
Related
I hope somebody can explain me some kind of solution:)
I've been working on app UI using Flutter and there are 2 types of a button in that app design on Figma, each has its accent (there're 3 for now), which just define the button's heigth.
Those 2 types of button and accents
So those buttons have different emphasis over the app and I need to somehow manage it following the best coding practices.
Well for now I simply created enum, which contains accent variation:
enum ButtonAccent {
primary,
secondary,
tertiary,
}
class FilledButton extends StatelessWidget {
final String text;
final IconData icon;
final Color backgroundColor;
final Color foregroundColor;
// defines a button height following Figma design. By default 'buttonAccent = ButtonAccent.tertiary', which sets 'heigth = 46'
final ButtonAccent buttonAccent;
final VoidCallback onPressed;
const FilledButton(
{super.key,
required this.text,
required this.icon,
this.backgroundColor = ColorConstants.kCallToAction,
this.foregroundColor = ColorConstants.kText,
this.buttonAccent = ButtonAccent.tertiary,
required this.onPressed});
// method that checks accent
double _buttonHeigthFromAccent() => buttonAccent == ButtonAccent.primary
? 72.0
: buttonAccent == ButtonAccent.secondary
? 60.0
: buttonAccent == ButtonAccent.tertiary
? 48.0
: throw Exception('Wrong ButtonAccent value');
#override
Widget build(BuildContext context) {
return ElevatedButton.icon(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
minimumSize: Size(double.infinity, _buttonHeigthFromAccent()),
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
textStyle: const TextStyle(color: ColorConstants.kText),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0))),
icon: Icon(icon),
label: Text(text),
);
}
}
Then my FilledButton contains method that checks the accents passed via constuctor and returning the proper heigth.
But! There're plenty of cons in my opinion:
if we consider to change not just height of a button but style in general (color, shape etc), it will lead us to overwrite the whole button implementation and ButtonAccent enum;
not sure that all principles of SOLID are met;
I put _buttonHeigthFromAccent() in both CustomOutlinedButton and FilledButton (added the code of FilledButton only as it doesn't differ much) which is bad idea as well;
I think it would be better to create an abstract class (interface) so I could implement it for my needs.
However I am not sure about it, it can be just extra, pointless work
You can use enhanced enum, it is comes by default from v2.17.0
enum ButtonAccent {
primary(72.0),
secondary(60.0),
tertiary(48.0);
const ButtonAccent(this.size);
final double size;
}
And use the size like
minimumSize: Size(double.infinity, buttonAccent.size),
I have a class for menu-entries:
abstract class MenuSelection {
MenuSelection({required this.label});
final String label;
}
and two classes that inherit from this class:
///for menu entries with an image
abstract class ImageSelection extends MenuSelection {
ImageSelection({required this.image, required super.label});
final String image;
}
///for menu entries with an icon
abstract class IconSelection extends MenuSelection {
IconSelection({required this.iconData, required super.label});
final IconData iconData;
}
I want to have one DropDownMenu and react depending on the given class:
class _DropDownMenuItem extends StatelessWidget {
const _DropDownMenuItem({super.key, required this.selection});
final MenuSelection selection;
#override
Widget build(BuildContext context) {
return Row(
children: [
if (selection is ImageSelection)
Image.asset(
selection.image,
width: 30,
)
else if (selection is IconSelection)
Icon(
selection.iconData,
size: 30,
)
else
Container(),
const SizedBox(
width: 10,
),
Text(selection.label.tr()),
],
);
}
}
I thought it must work that way because it works for example when used with Bloc and getting the current type of state...
Actually AndroidStudio tells me that class MenuSelection has no image or icon property instead of recognizing that this is ImageSelection or IconSelection.
Can someone explain this behavior?
The implicit type promotion only works for local variables, not for class fields. You can read this answer if you need a detailed explanation and a possible solution.
I came from a React world and trying to get my head around Flutter and Dart.
I'm using the Text widget with the same parameters a lot, so it seems reasonable to think of a way to reuse code. I created a wrapper that uses it:
import 'package:flutter/material.dart';
TextStyle getThemeProperty(type, TextTheme textTheme) {
switch (type) {
case 'headline1':
return textTheme.headline1;
case 'headline2':
return textTheme.headline2;
case 'headline3':
return textTheme.headline3;
default:
return textTheme.bodyText2;
}
}
class CustomText extends StatelessWidget {
const CustomText({Key key, this.type, this.text, this.color}) : super(key: key);
final type;
final text;
final color;
#override
Widget build(BuildContext context) {
var textTheme = Theme.of(context).textTheme;
var style = getThemeProperty(type, textTheme);
if (this.color != null) style.color = this.color;
return Text(
this.text,
style: style,
);
}
}
// Usage
CustomText(
text: 'Some Heading',
type: 'headline2',
color: Colors.black
)
The idea is to set the color if the color property is passed as a parameter, but Dart's compiler doesn't like it. It throws me the error: ''color' can't be used as a setter because it's final.
Try finding a different setter, or making 'color' non-final.'
I'm planning to do the same to fontWeight and textAlign properties as well. How am I able to make this work, I mean, to add new props to the style object on demand?
The reason why the dart compiler is unhappy is just because the color property of the TextStyle is declared as final. Therefore to use a new color, you have to create a new instance of the TextStyle.
Luckily, the TextStyle class comes with a copyWith method that returns an edited copy of your TextStyle
final type;
final text;
final color;
#override
Widget build(BuildContext context) {
var textTheme = Theme.of(context).textTheme;
var style = getThemeProperty(type, textTheme);
return Text(
this.text,
// Added this...
style: style.copyWith(color: color ?? style.color),
);
}
As a side note, when making reusable widgets, it's always a good idea to type your parameters. This is because any type of variable can be used. So instead of passing a String for text, you may pass an int
// DON'T DO THIS
final type;
final text;
final color;
// DO THIS
final String type;
final String text;
final Color color;
Also adding the this keyword to reference a variable in a class without variable shadowing is unnecessary.
// DON'T
this.text
// DO
text
I have created a CardText as a stateless widget and I will use it whenever I would be needing it. But I have a problem. As y'all can see, there are properties that I haven't marked as #required. What I want is these properties have a pre-defined value. Like, suppose the color property, it should be 0xFFFFFFFF until and unless I want somewhere to be as 0xFF000000. But these are final properties that can't be assigned on the basis of ??= method. Yes, I know, marking these properties as #required will require me to define each and every property whenever I call it. But having a pre-defined value will help me a lot to save time and a few lines of code.
Well, any expert out there, I don't know how to express the problem, so feel free to change the title. Thank you.
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
class CardText extends StatelessWidget {
final String data;
final int color;
final int fontSize;
final FontWeight fontWeight;
const CardText(
this.data, {
this.color,
this.fontSize,
this.fontWeight,
});
#override
Widget build(BuildContext context) {
return Text(
data,
style: GoogleFonts.openSans(
textStyle: TextStyle(
fontSize: fontSize,
fontWeight: fontWeight,
color: Color(color),
),
),
);
}
}
If your arguments are optional then you can give default it right away, like following
const CardText({
this.data,
this.color = 0xFFFFFFFF,
this.fontSize = 14,
this.fontWeight,
})
You can use the : colon syntax:
const CardText(
this.data, {
this.color,
this.fontSize,
this.fontWeight,
}) : color = 0xFFFFFFFF, data = "data"
The code after the colon will be executed before the code inside the curly brackets. From the linked question
The part after : is called "initializer list. It is a ,-separated list
of expressions that can access constructor parameters and can assign
to instance fields, even final instance fields. This is handy to
initialize final fields with calculated values.
I know about the theme object "ThemeData" and i'm making use of that as well, but alas the need for extending it has arisen.
What I'm trying to accomplish is defining style sets to reference throughout my app. For example i have this
child: Text(
advisoryServiceStatus[item.status - 1],
style: TextStyle(
color: Color.fromRGBO(0, 0, 0, 0.6),
fontSize: 12,
fontWeight: FontWeight.w500),
),
and i want to move the TextStyle in a file so i can do something like
child: Text(
advisoryServiceStatus[item.status - 1],
style: extendedThemeConfig.textStyles.mutedText,
but i have troubled properly defining my style object. Here's what i tried. Maybe i shouldn't be using classes, but i haven't managed to define them as objects. (my understanding of the concepts is a bit shabby)
This is how i tried to define my extendedThemeConfig
class TextStyles {
final TextStyle mutedText = TextStyle(
color: Color.fromRGBO(0, 0, 0, 0.6),
fontSize: 12,
fontWeight: FontWeight.w500);
}
class ExtendedThemeConfig {
TextStyles textStyles;
}
const extendedThemeConfig = ExtendedThemeConfig;
Why your approach doesn't work
It probably does work, but features like hot-reload aren't supported, because you introduce global state to your app, which is often not what you want.
So, how to do it better?
I already answered a similar question here more elaborately, but here's a version adapted to your problem:
Because Flutter is open source, we can just look at how the Theme is implemented and copy that code to create a custom widget that functions just like a Theme.
Here's a boiled-down version:
#immutable
class MyThemeData {
MyThemeData({
this.mutedText,
});
final TextStyle mutedText;
}
class MyTheme extends StatelessWidget {
MyTheme({
Key key,
#required this.data,
#required this.child,
}) : super(key: key);
final MyThemeData data;
final Widget child;
static MyThemeData of(BuildContext context) {
return (context.ancestorWidgetOfExactType(MyTheme) as MyTheme)?.data;
}
#override
Widget build(BuildContext context) => child;
}
Now, you can just wrap the MaterialApp in a MyTheme widget:
MyTheme(
data: MyThemeData(
mutedText: ...,
),
child: ... (here goes the MaterialApp)
)
Then anywhere in your app, you can write MyTheme.of(context).mutedText.
You can adapt the MyThemeData class to your needs, storing anything you want.