What's a good way of defining theme objects in Flutter(Dart)? - flutter

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.

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

How to manage more than 1 type(accent) of button properly?

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),

Dart: avoid getters/methods duplication for proxy object

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,
);

What is the best way to reuse a widget in 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.

How to pre-define propeties in classes Flutter

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.