When looking at #13 I very much liked the concept of a Style
class which bundles css properties in a simple way. The advantages of such a class over a raw Map<String, String
are really good:
- strongly typed properties
- instead of everything being a
String
, you can have types for numbers, enums, units or custom classes for things like Color
- IDE support
- you get hints which values a property can have and how to construct them
- properties can be documented and viewed from inside the IDE (no googling css properties)
- e.g. for
padding
having the EdgeInsets
class with separate constructors for .only
, .symmetric
or .all
translates well to the different shorthand versions
- you only write dart code instead of having to write css
- this is especially useful for flutter devs or others that are not fluid in css
Proposal / Discussion
When implementing such a class, we need to make sure that it is easy to use and understand. It should be possible to compose, change, and combine styles in an easy way.
Grouping Styles
Having a single Style
class with all implemented properties as fields will get way to large very quickly. This negates the positive effect of having IDE support, since you get a very long and convoluted list of properties. I like the way how #13 groups semantically related styles together into sub-classes, like TextStyle
, BackgroundStyle
or BoxStyle
.
In the pr, combining those style groups is done through the MultipleStyle
class, which takes a list of BaseStyle
s. I don't really like this approach since it introduces an additional class (or two with the BaseStyle class) just to allow for settings styles from different semantic groups. Also I think this is not ideal since the user could e.g. put two TextStyle
s which would not make sense. Also the user does not have help from the IDE in which style groups exist and can be used.
Instead an approach would be to have the Style
class accept instances of the grouped sub-classes, something like:
var style = Style(
text: TextStyle(...),
background: BackgroundStyle(...),
box: BoxStyle(...),
);
A disadvantage of this approach is that it is not extensible by the user, while with the MultipleStyle
the user could create an own subclass of the BaseStyle
class to be passed to the styling list. Also it lacks a way of providing raw styles in order to account for properties that are missing from the existing style classes (could be solved with an additional Map<String, String> raw
property).
Another approach could be to use a builder-like pattern to compose styles. Instead or providing style properties through the constructor, there could be methods on the Style
class to incrementally compose a style:
var style = Style()
.text(...)
.background(...)
.box(...)
.raw(...);
Internally this would probably just keep a Map<String, String>
instead of keeping instances of separate classes.
This would allow for easy extension by the user through darts extension methods. It works also great with IDE code completion. A disadvantage would be that this would not allow to use styles as a const
variable, which might be desirable when you have a set of standard styles you want to use in your app. It's also not very "Fluttery".
Modifying / Composing Styles
With the first approach, in order to modify the style instance the Style
class as well as all sub-classes would have to implement a copyWith
method. Again this isn't really extensible by the user and double the work to maintain. Merging two styles would also not be easy and requires to merge all sub-classes individually.
The second approach would be modifiable by default, since you could just call e.g. the .text()
method again which would override any existing text-styles. It would also be easier to merge two styles by just merging the internal Map
s of both.
Both approaches would still be immutable. With "modifying" I mean returning a new instance with adjusted styles, not mutating the existing instance.
A third way
While I like the second approach more because of its advantages, it does not feel very "Fluttery". So I would like to explore a third option combining all the above.
There could be separate sub-classes for semantic style groups, but those are private and hidden behind named constructors of the actual Style
class. This would then look like Style.text(...)
, Style.background(...)
, Style.raw(...)
, etc. for the user, which has IDE support for code completion. The user could then still create a custom sub-class extending Style
.
Combining styles could be done through a Style.combine(Iterable<Style> styles)
constructor, removing the need for a separate class to combine multiple styles. Modifying a style would be done through the same method, by combining the original style with a new style, overriding the existing properties.
Together this would look like:
var backgroundStyle = Style.background(...);
var style = Style.combine([
backgroundStyle,
Style.text(...),
Style.box(...),
Style.raw({'color': 'blue'}),
MyCustomStyle(),
]);
A component (e.g. 'DomComponent') would then accept a single Style style
property.