In Flutter, everything is a widget. You can freely compose them and create your own which is awesome! However, there are certain inconsistencies and lacking features that make styling and positioning widgets a pain. What's more, it's pretty tough to extract a style of a widget. What if you want to reuse just the padding and border radius on multiple different widgets? Well, you're out of luck...
Unless you use a package called division which allows you to do just that! Extract styles and make styling an enjoyable endeavor by streamlining certain processes.
The project we will build
A hypothetical designer has designed a beautiful ? UI component for displaying messages to the user. It has all the bells and whistles, like ripple effect, elevation, round corners and the list goes on...
Good, thinking that creating a MessageDisplay
widget looking precisely like the spec will be an easy task, we hop straight into implementation:
main.dart
class MessageDisplayClassic extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Container(
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
'Lorem ipsum dolor sit amet...',
style: TextStyle(
color: Colors.white,
fontSize: 30,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic,
),
),
),
width: 350,
decoration: BoxDecoration(
color: Colors.green.shade800,
borderRadius: BorderRadius.circular(8),
),
),
);
}
}
The widget above is starting to take shape, but it lacks elevation and ripple effect (aka an InkWell
), scale and also rotation. Also, we'd need to react to taps and somehow animate properties.
Could we do all that using the standard Flutter way of styling widgets? Sure, but look a the code above. Even now it's looking like a mess and simply extracting all of the objects into helper methods wouldn't help a lot.
A better way to style
The division package sets out to relieve app developers from problems and confusions like the following:
- When you want to center a Flutter widget, you have to wrap it inside a
Center
widget. Trying to add some padding? Wrap it inside aPadding
widget. However, do you want to change the color of aContainer
, let's say? Well, you cannot wrap it inside a Color widget... Instead you have to set thecolor
property. - Do you want to set an
elevation
on aContainer
? Well, you're out of luck! That's only supported on material widgets, such as aCard
.
As you can see, many of Division's features will be immensely helpful in making the widget we're building possible without any hair loss.
Using Division
Division borrows from CSS in a good way. With it, applying a padding is no different from changing the color! Just set properties on a ParentStyle
object and you're set. No more wrapping into a Padding
widget or other craziness. Division also provides a TxtStyle
class made specifically for styling text.
First, add division to the project:
pubspec.yaml
dependencies:
flutter:
sdk: flutter
division: ^0.8.2
Let's put this package into practice and recreate the code above using Division.
main.dart
class MessageDisplayDivision extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Parent(
child: Container(
child: Txt(
'Lorem ipsum dolor sit amet...',
style: TxtStyle()
..textColor(Colors.white)
..fontSize(30)
..bold()
..italic()
..textAlign.start(),
),
),
style: ParentStyle()
..alignment.center()
..padding(all: 16)
..width(350)
..background.color(Colors.green.shade800)
..borderRadius(all: 8),
);
}
}
The code above looks different from the previous one, but after you dissect it for a bit, you'll realize just how simple it is to use Division. Define a Parent
which takes in a child that you want to style (verb ?) and obviously, provide the actual style (noun ?).
Styling a text is very similar, just instead of the regular Text
widget, use Division's wrapper called Txt
and apply a TxtStyle
instead of a ParentStyle
.
Also, where have all of our trusted widget friends like Center
and Padding
go? They are now applied on the ParentStyle
, just like all of the other style-related attributes.
In addition to having all the styling readably defined in one place, we can now extract the styling and use it across multiple widgets in a manageable way! Consider how clean this code looks...
main.dart
class MessageDisplayDivisionSimple extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
return Parent(
child: Container(
child: Txt(
'Lorem ipsum dolor sit amet...',
style: textStyle,
),
),
style: containerStyle,
);
}
}
...after we put the styles into variables literally with zero hassle ?. You know, it's not like we have to manage the Container
being wrapped inside a Center
widget or anything like that ?
You can put the styles into some global static final
fields or make them local to one widget, it's up to you.
main.dart
class MessageDisplayDivisionSimple extends StatelessWidget {
final containerStyle = ParentStyle()
..alignment.center()
..padding(all: 16)
..width(350)
..background.color(Colors.green.shade800)
..borderRadius(all: 8);
final textStyle = TxtStyle()
..textColor(Colors.white)
..fontSize(30)
..bold()
..italic()
..textAlign.start();
...
}
What we've accomplished with Division by now is great, but we're just breaking even with what we had implemented using the standard Flutter way of doing things. Let's add all the other styling goodness which would be tougher to do without Division, namely elevation, ripple effect, rotation, scaling and more!
Complex styles with Division
Let's continue with the code we currently have. Introducing the initial elevation and enabling the ripple effect is extremely straight-forward. We'll also want to enable automatic animation. In addition to all this, we will later on want to animate the rotation. Let's choose degrees to be our unit.
main.dart
final containerStyle = ParentStyle(angleFormat: AngleFormat.degree)
..alignment.center()
..padding(all: 16)
..width(350)
..background.color(Colors.green.shade800)
..borderRadius(all: 8)
..elevation(3)
..ripple(true)
..animate(100 /*milliseconds*/, Curves.decelerate);
It's great that the message display widget now has a ripple and a static elevation, but the real question is: "How do we animate stuff?". With setState
and updating the desired values inside the build
method, of course. First though, let's deal with the taps!
Handling gestures
With Division, you don't need to wrap your widgets into a GestureDetector
. Just set up a GestureClass
instead! Inside the callback functions onTapDown
, onTapCancel
and, just to be safe, onTapUp
, we'll simply change the value of a single local field called isBeingTapped
. This requires us to switch over to a StatefulWidget
, of course.
main.dart
class _MessageDisplayDivisionState extends State<MessageDisplayDivision> {
bool isBeingTapped = false;
...
@override
Widget build(BuildContext context) {
return Parent(
...
gesture: GestureClass()
..onTapDown((_) {
setState(() {
isBeingTapped = true;
});
})
..onTapUp((_) {
setState(() {
isBeingTapped = false;
});
})
..onTapCancel(() {
setState(() {
isBeingTapped = false;
});
}),
);
}
}
Having the taps sorted out, how do we animate the widget?
Animating with Division
We've already enabled automatic animation by calling the following method on the style:
animate(100 /*milliseconds*/, Curves.decelerate)
Animation is now a question of updating the values we want to update. In our case, we want to animate elevation, scale and rotation. We can update just these three values by cloning the extracted containerStyle
. If you specify your style directly without extracting it into a separate field, you'd modify without cloning. For clarity, here is the full code with the relevant parts highlighted:
main.dart
class MessageDisplayDivision extends StatefulWidget {
@override
_MessageDisplayDivisionState createState() => _MessageDisplayDivisionState();
}
class _MessageDisplayDivisionState extends State<MessageDisplayDivision> {
bool isBeingTapped = false;
final containerStyle = ParentStyle(angleFormat: AngleFormat.degree)
..alignment.center()
..padding(all: 16)
..width(350)
..background.color(Colors.green.shade800)
..borderRadius(all: 8)
..elevation(3)
..ripple(true)
..animate(100 /*milliseconds*/, Curves.decelerate);
final textStyle = TxtStyle()
..textColor(Colors.white)
..fontSize(30)
..bold()
..italic()
..textAlign.start();
@override
Widget build(BuildContext context) {
return Parent(
child: Container(
child: Txt(
'Lorem ipsum dolor sit amet...',
style: textStyle,
),
),
style: containerStyle.clone()
..elevation(isBeingTapped ? 10 : 3)
..scale(isBeingTapped ? 1.1 : 1)
..rotate(isBeingTapped ? -10 : 0),
gesture: GestureClass()
..onTapDown((_) {
setState(() {
isBeingTapped = true;
});
})
..onTapUp((_) {
setState(() {
isBeingTapped = false;
});
})
..onTapCancel(() {
setState(() {
isBeingTapped = false;
});
}),
);
}
}
And that's how simple, extensible, readable and maintainable it is do create beautifully styled widgets with Division.
What you learned
Division is a library aiming to simplify the process of styling widgets in Flutter. It borrows the good parts of CSS, where everything is defined inside a class. Here, everything gets defined inside a style. For styling regular widgets, there's the Parent
wrapper and ParentStyle
. For text, Division comes with a Txt
wrapper and a TxtStyle
. All in all, this package is already helping us, Flutter developers, a ton and I'm very optimistic with regards to its further development.
in the pub dev page of Division it says “Please check out styled_widget which is a replacement of Division!”. does this mean Division is nomore? Styled_Widget also looks cool though…