Division – Style Your Flutter Widgets Without Confusion (Flutter Tutorial)

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 effectelevationround 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 ani​​​​mate properties. 

The current look of the widget

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 a Padding widget. However, do you want to change the color of a Container, let's say? Well, you cannot wrap it inside a Color widget... Instead you have to set the color property.
  • Do you want to set an elevation on a Container? Well, you're out of luck! That's only supported on material widgets, such as a Card.

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.

Matej Rešetár
 

Matej is an app developer with a knack for teaching others. If he's not programming, making tutorials or doing other business, he's mostly working out, listening to audiobooks and taking cold showers.

>