Flutter custom widget styling - flutter

I'm working on a big app tightly with the designer.
I was a part of the style guide and the component design process,the designer handed off his work and now its my turn to translate some prototypes and styles to real flutter app.
I'm having a really hard time translating the style guide to flutter widgets.
For example, I have a custom chip widget created from scratch, with many style properties.
There are two main different styles for this chip (normal and highlighted), and the chip is used widely (I believe more than 50 references), excluding other more complex widgets using it.
I tried :
Creating a style class for each big widget, and created extension on ThemeData.
class MyCustomChip extends StatelessWidget {
Widget build(BuildContext context) {
final style = Theme.of(context).myCustomChip;
/** return the custom chip **/
}
}
class MyCustomChipStyle {
/** many style properties **/
}
extension MyCustomChipThemeData on ThemeData {
MyCustomChipStyle get myCustomChip => ...
}
Created a static style class, and passed relevant style properties every time the widget used.
class MyCustomChip extends StatelessWidget {
final Color color;
final BorderRadius borderRadius;
/** many more styling properties **/
}
abstract class MyCustomChipValues {
static const BorderRadius kBorder = /** Getting value from a predefined radiuses **/
static const Color kColor = /** Getting value from a predefined palette **/
/** Many more **/
}
class WidgetUsingTheChip extends StatelessWidget {
Widget build(BuildContext context) {
/** those lines will be duplicated alot **/
return MyCustomChip(
color: MyCustomChipValues.kColor,
borderRadius: MyCustomChipValues.kBorder,
/** Many more **/
);
}
}
Used all the relevant style properties inside the custom widget, and didn't expose them.
abstract class MyCustomChipValues {
static const BorderRadius kBorder = /** Getting value from a predefined radiuses **/
static const Color kColor = /** Getting value from a predefined palette **/
/** Many more **/
}
class MyCustomChip extends StatelessWidget {
Widget build(BuildContext context) {
/** making it virtually impossible to change style depending on state **/
return Container(
color: MyCustomChipValues.kColor,
/** Many more **/
);
}
}
class WidgetUsingTheChip extends StatelessWidget {
Widget build(BuildContext context) {
return MyCustomChip();
}
}
I have defined other style classes :
Insets (various paddings and margins)
Colors (our palette, with buttons, chips, input, scaffold, etc..)
BorderRadiuses
Shadows
Animations (duration and curve)
Guidelines (based on the gutter and the design grid we prototyped with)
Other classes...
I cant wrap my head on which of the following is more easy to maintain and refactor in the time of need..
Any suggestion on how to manage this whole Style guide -> code will be helpful,
especially on how to manage custom widget styles.
Thanks !

Basically you need to understand the concept of custom widgets and know the different types of chips that exist.
You can have several types of bullets - choice, filter, action, input - Chips. Chips - Material Design
From the different types of styles that material design offers, you adapt this to the widgets you receive from your design team. For example wrap your bullets in a Column to have an extra Label.
class MyActionChip extends StatelessWidget {
/// Default chip have a string label
const MyActionChip({
required this.label,
required this.onPressed,
Key? key,
this.onTapAvatar,
}) : child = null, super(key: key);
/// Default chip have a widget label
const MyActionChip.child({
required this.child,
required this.onPressed,
Key? key,
this.onTapAvatar,
}) : label = null, super(key: key);
final String? label;
final Widget? child;
final VoidCallback? onTapAvatar;
final Function()? onPressed;
#override
Widget build(BuildContext context) {
return InputChip(
backgroundColor: MyColors.milk,
shape: const RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(MyConstants.borderRadiusMedium)),
side: BorderSide(color: MyColors.anthracite20),
),
avatar: const Icon(
MyIcons.plus,
size: 18,
color: MyColors.anthracite,
),
labelPadding:
const EdgeInsets.only(right: SdConstants.contentPaddingMedium),
label: child ??
Text(
label!,
style: const TextStyle(
color: MyColors.anthracite,
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
onPressed: onPressed,
);
}
}
In complex cases, you can build a custom chip according to the complexity.
Note: the parameters that vary according to the screens and callbacks are simply exposed outside the constructor. You need several constructors/classes to customize all your chips.

I would recommend using InheritedWidget, then you don't have to rely on external packages.
For example:
class CustomStyle extends InheritedWidget {
const CustomStyle({
Key? key,
required this.data,
required Widget child,
}) : super(key: key, child: child);
final StyleData data;
static CustomStyle of(BuildContext context) {
final CustomStyle? result =
context.dependOnInheritedWidgetOfExactType<CustomStyle>();
assert(result != null, 'No CustomStyle found in context');
return result!;
}
#override
bool updateShouldNotify(CustomStyle oldWidget) => data != oldWidget.data;
}
You could create these and add to your CustomStyle in whatever way you want, maybe have multiple class depending on the widget, or separate them by colors.
class StyleData {
const StyleData({
required this.color,
required this.padding,
});
final Color color;
final EdgeInsets padding;
}
Wrap your MaterialApp with your CustomStyle, pass your data.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return CustomStyle(
data: const StyleData(
color: Colors.blue,
padding: EdgeInsets.zero,
),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
Then you can access you styling in all your custom widgets:
class CustomContainer extends StatelessWidget {
const CustomContainer({Key? key, this.color, this.padding}) : super(key: key);
final Color? color;
final EdgeInsets? padding;
#override
Widget build(BuildContext context) {
return Container(
color: color ?? CustomStyle.of(context).data.color,
padding: padding ?? CustomStyle.of(context).data.padding,
child: const Text('Hello'),
);
}
}
Any questions just let me know.

Related

Flutter: howto return the codePoint value from an Icon widget?

I want to bypass an Icon Widget to a custom widget and get the Icons codePoint value make some changes and return the new Icon Widget.
class NewCustomWidget extends StatefulWidget {
const NewCustomWidget({
Key? key,
this.mySize,
this.myIcon,
}) : super(key: key);
final double? mySize;
final Icon? myIcon;
#override
_NewCustomWidgetState createState() => _NewCustomWidgetState();
}
class _NewCustomWidgetState extends State<NewCustomWidget> {
#override
Widget build(BuildContext context) {
return Icon(
???? howto bypass the widget.myIcon's codePoint/IconData to the new Icon ????
color: Colors.black,
size: widget.mySize,
);
}
}
I tried to use the widget.myIcon.toString() but I get only the String "widget"
Icon takes IconData. You can do
return Icon(
widget.myIcon?.icon,
color: Colors.black,
size: widget.mySize,
);

Not being able to understand the flutter docs

I was referencing to the Flutter Docs, but I am not able to correctly read them. What I am trying to say is that I am not able to correctly understand them. I am having questions like "How would I know if a property accepts Widget or double or MaterialStateProperty?"
I was trying to understand the elevation property under ButtonStyle class under ButtonStyleButton class. It is written as:
elevation → MaterialStateProperty<double?>?
The elevation of the button's Material.
I can't understand this syntax. What will be the input of elevation if I am defining the custom ButtonClassButton class as following:
class RoundIconButton extends StatelessWidget {
const RoundIconButton({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ButtonStyleButton(
onPressed: () {},
style: ButtonStyle(
elevation: ?????????????,
),
);
}
}
The MaterialState enum provides interactive states that some of the Material widgets can take on when receiving input from the user. The MaterialStateProperty<T> class is an interface for classes that resolve to a value of type T based on a widget's interactive "state".
To provide more clarification, please refer the Static Methods under MaterialStateProperty class here.
If you have a single value for all states (say 10.0 as the double value), you can set the elevation as follows:
class RoundIconButton extends StatelessWidget {
const RoundIconButton({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ButtonStyleButton(
onPressed: () {},
style: ButtonStyle(
elevation: MaterialStateProperty.all(10),
),
);
}
}

Should I add class type when I use final with local widget in flutter?

Code A:
class MyContainer extends StatelessWidget {
const MyContainer({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
// No class type
final container = Container(
width: 100,
height: 100,
color: Colors.red,
);
return container;
}
}
Code B:
class MyContainer extends StatelessWidget {
const MyContainer({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
// Add class type
final Widget container = Container(
width: 100,
height: 100,
color: Colors.red,
);
return container;
}
}
I prefer Code A, but I saw some flutter source code in AppBar:
final Widget toolbar = NavigationToolbar(
leading: leading,
middle: title,
trailing: actions,
centerMiddle: widget._getEffectiveCenterTitle(theme),
middleSpacing: widget.titleSpacing,
);
It's like Code B, why flutter official add class type? For performance? For more readable? or other reasons?
Should I add class type when I use final with local widget?
Dart is type-safe and you decide to add a type or not. Type annotations are optional, because Dart VM performs runtime checks and to ensure variable’s value always matches the variable’s static type. See this for more on Dart Type System.

Creating custom widgets with multiple constructors

So I am trying to create a custom widget in flutter that has multiple constructors.
Essentially I am creating an arrow button but want to be able to create the button with arrow icons facing in different directions.
Currently my code looks like this:
class RotateButton extends StatefulWidget {
RotateButton.left() {
_RotateButtonState createState() => _RotateButtonState.left();
}
RotateButton.right() {
_RotateButtonState createState() => _RotateButtonState.right();
}
#override
_RotateButtonState createState() => _RotateButtonState();
}
class _RotateButtonState extends State<RotateButton> {
IconData icon;
_RotateButtonState();
_RotateButtonState.left() {
icon = Icons.arrow_back;
}
_RotateButtonState.right() {
icon = Icons.arrow_forward;
}
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: kPrimaryColor,
borderRadius: BorderRadius.circular(20),
),
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Icon(
icon,
size: 70,
),
),
);
}
}
Every time I use my widget it just defaults to the default constructor and shows no Icon child.
Is there a way to build a class without making a default constructor.
Also is there a way I can build this widget without using a stateful widget as it kind of just overcomplicates it.
I am getting a message that says:
The declaration 'createState' isn't referenced
This message is coming up next to the named constructors in the rotatebutton class.
Any help is very much appreciated.
Use something like this:
class SomeWidget extends StatelessWidget {
final bool isRight;
const SomeWidget({Key key, this.isRight}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Icon(isRight?Icons.arrow_forward:Icons.arrow_back),
);
}
}

Flutter optionally include / exclude parent widget in widget tree

I have this widget tree
return Container(
color: Colors.blue,
child: Container(
child: Text("Child"),
),
);
Is there a way to remove a parent widget from the tree or conditionally include it?
For example if a state variable such as includeBlueContainer was false, I would like to not render the blue container (but show everything else).
I couldn't achieve an optionally include reusable widget but I've been using this pattern which does achieve what I wanted to achieve. I haven't given this a great amount of thought but I still feel there is a better solution somewhere.
class MyContainer extends StatelessWidget {
final Widget child;
final bool isIncluded;
MyContainer({this.child, this.isIncluded = true});
Widget build(BuildContext context) {
if (!isIncluded) return child;
return Container(
color: Colors.blue,
child: child,
);
}
}
Edit: I made a package out of this: https://pub.dev/packages/conditional_parent_widget
Which you can use like:
import 'package:flutter/widgets.dart';
import 'package:conditional_parent_widget/conditional_parent_widget.dart';
// ...
return ConditionalParentWidget(
condition: includeBlueContainer,
child: Text("Child"),
parentBuilder: (Widget child) => Container(
color: Colors.blue,
child: child,
),
);
Internally it is just:
import 'package:flutter/widgets.dart';
class ConditionalParentWidget extends StatelessWidget {
const ConditionalParentWidget({
Key? key,
required this.condition,
required this.child,
required this.parentBuilder,
}) : super(key: key);
final Widget child;
final bool condition;
final Widget Function(Widget child) parentBuilder;
#override
Widget build(BuildContext context) {
return condition ? this.parentBuilder(this.child) : this.child;
}
}