Slide 1

Slide 1 text

Supercharge Flutter Declarative UI with code generation Emanuele Papa droidcon London 2021

Slide 2

Slide 2 text

Working at Zest One in Chiasso, Switzerland Who I am? Emanuele Papa, Android Developer Find me at www.emanuelepapa.dev

Slide 3

Slide 3 text

Declarative UI Imperative UI -> Declarative UI Jetpack Compose and SwiftUI Unfortunately, not perfect yet No more XML or UIKit

Slide 4

Slide 4 text

Padding in Flutter Padding( padding: EdgeInsets.all(16.0), child: Text("Hello droidcon!") );

Slide 5

Slide 5 text

What we want - Make the code easy to write - Avoid errors caused by distraction - Minimize code duplication - Make the IDE our best friend - Improve our productivity

Slide 6

Slide 6 text

Looking for improvements Someone else experienced the same struggle Some libraries are available on GitHub

Slide 7

Slide 7 text

It's an improvement, but you still need to add the Padding value manually The missing thing PaddingTop(16.0, child: Text("Hello droidcon!"));

Slide 8

Slide 8 text

Design System Padding No need to add the Padding value manually, it's specified in the class name MediumTopPadding(child: Text("Hello droidcon!"));

Slide 9

Slide 9 text

How to create these widgets?

Slide 10

Slide 10 text

Paddinger

Slide 11

Slide 11 text

How to use Paddinger? dependencies: paddinger_annotations: [latestVersionHere] dev_dependencies: paddinger: [latestVersionHere] Add these dependencies to your pubspec.yaml

Slide 12

Slide 12 text

How to use Paddinger? Create a paddings.dart file which contains your PADDING_ definitions @paddinger const double PADDING_MEDIUM = 16.0; @paddinger const double PADDING_LARGE = 24.0;

Slide 13

Slide 13 text

How to use Paddinger? Add material or cupertino import Add part directive // ignore: unused_import import 'package:flutter/material.dart'; part 'paddings.g.dart';

Slide 14

Slide 14 text

Start the code generation Now run flutter pub run build_runner build --delete-conflicting- outputs

Slide 15

Slide 15 text

What's the result? MediumAllPadding MediumLeftPadding MediumTopPadding MediumRightPadding MediumBottomPadding MediumHorizontalPadding MediumVerticalPadding MediumLeftTopPadding MediumLeftBottomPadding MediumRightTopPadding MediumRightBottomPadding paddings.g.dart LargeAllPadding LargeLeftPadding LargeTopPadding LargeRightPadding LargeBottomPadding LargeHorizontalPadding LargeVerticalPadding LargeLeftTopPadding LargeLeftBottomPadding LargeRightTopPadding LargeRightBottomPadding

Slide 16

Slide 16 text

What's the result? class MediumAllPadding extends Padding { const MediumAllPadding({Key? key, required Widget child}) : super( key: key, padding: const EdgeInsets.all(PADDING_MEDIUM), child: child); }

Slide 17

Slide 17 text

Do not add it to your version control system How to use them Just import paddings.dart in your widgets, it also contains all the generated classes created in paddings.g.dart

Slide 18

Slide 18 text

Let's deep dive inside paddinger source code!

Slide 19

Slide 19 text

Paddinger source code Monorepo with 2 projects: - paddinger - paddinger_annotations

Slide 20

Slide 20 text

Paddinger source code ///Annotation used by paddinger to identify PADDING_ constants class Paddinger { const Paddinger(); } ///Helper object for [Paddinger] annotation const paddinger = Paddinger(); paddinger.dart

Slide 21

Slide 21 text

Paddinger source code dependencies: analyzer: ^1.5.0 build: ^2.0.0 source_gen: ^1.0.0 recase: ^4.0.0 paddinger_annotations: ^0.1.1 #local development #paddinger_annotations: # path: ../paddinger_annotations/ dev_dependencies: test: 1.16.8 pubspec.yaml

Slide 22

Slide 22 text

Paddinger source code builders: paddinger_generator: import: "package:paddinger/paddinger.dart" builder_factories: ["paddingerGenerator"] build_extensions: {".dart": [".paddinger.g.part"]} build.yaml

Slide 23

Slide 23 text

Paddinger source code ///Paddinger generator used with [Builder] Builder paddingerGenerator(BuilderOptions options) => SharedPartBuilder([PaddingerGenerator()], 'paddinger_generator'); paddinger.dart

Slide 24

Slide 24 text

Paddinger source code class PaddingerGenerator extends GeneratorForAnnotation { @override FutureOr generateForAnnotatedElement( Element element, ConstantReader annotation, BuildStep buildStep) { final constantName = element.name!; validatePaddingConstantName(constantName); final generatedName = toNameForGeneration(constantName); return generatePaddingWidgets(generatedName, constantName); } } paddinger_generator.dart

Slide 25

Slide 25 text

Paddinger source code String generatePaddingWidgets(String paddingName, String constantName) { return ''' ${_addStartComment(paddingName)} ${_addAllPadding(paddingName, constantName)} ${_addOnlyPaddings(paddingName, constantName)} ${_addSymmetricPaddings(paddingName, constantName)} ${_addMissingCombinationPaddings(paddingName, constantName)} ${_addEndComment(paddingName)} '''; }

Slide 26

Slide 26 text

Paddinger source code String _addAllPadding(String paddingName, String constantName) { return ''' class ${paddingName}AllPadding extends Padding { const ${paddingName}AllPadding({Key? key, required Widget child}) : super( key: key, padding: const EdgeInsets.all($constantName), child: child); } '''; }

Slide 27

Slide 27 text

Paddinger source code class MediumAllPadding extends Padding { const MediumAllPadding({Key? key, required Widget child}) : super( key: key, padding: const EdgeInsets.all(PADDING_MEDIUM), child: child); }

Slide 28

Slide 28 text

Paddinger source code String _addOnlyPaddings(String paddingName, String constantName) { final onlyPaddingKeys = PaddingDirection.values .map((paddingDirection) => paddingDirection.asPascalCase); final onlyPaddingWidgets = onlyPaddingKeys.map((paddingKey) => ''' class $paddingName${paddingKey}Padding extends Padding { const $paddingName${paddingKey}Padding({Key? key, required Widget child}) : super( key: key, padding: const EdgeInsets.only(${paddingKey.toLowerCase()}: $constantName), child: child); } ''').toList(); return onlyPaddingWidgets.join(); }

Slide 29

Slide 29 text

Paddinger source code class MediumLeftPadding extends Padding { const MediumLeftPadding({Key? key, required Widget child}) : super( key: key, padding: const EdgeInsets.only(left: PADDING_MEDIUM), child: child); } class MediumTopPadding extends Padding { const MediumTopPadding({Key? key, required Widget child}) : super( key: key, padding: const EdgeInsets.only(top: PADDING_MEDIUM), child: child); } ///two more classes: MediumRightPadding and MediumBottomPadding

Slide 30

Slide 30 text

How can you test the generated code?

Slide 31

Slide 31 text

Test the generated code test( 'GIVEN a valid padding constant name, WHEN validatePaddingConstantName(), THEN it returns normally', () { expect( () => validatePaddingConstantName("PADDING_MEDIUM"), returnsNormally); }); test( 'GIVEN a not valid padding constant name, WHEN validatePaddingConstantName(), THEN an exception is thrown', () { expect(() => validatePaddingConstantName("PADDIN_MEDIUM"), throwsA(isA())); });

Slide 32

Slide 32 text

Test the generated code test( 'GIVEN a valid padding constant name, WHEN toNameForGeneration(), THEN a valid name is generated', () { expect(toNameForGeneration("PADDING_MEDIUM"), "Medium"); }); test( 'GIVEN a valid padding constant name with two underscores, WHEN toNameForGeneration(), THEN a valid name is generated', () { expect(toNameForGeneration("PADDING_VERY_LARGE"), "VeryLarge"); });

Slide 33

Slide 33 text

Test the generated code test( 'GIVEN a valid paddingName and a valid constantName, WHEN generatePaddingWidgets(), THEN valid padding widgets are generated', () { expect(generatePaddingWidgets("Medium", "PADDING_MEDIUM"), ''' // ************************************************************************** // START Medium // ************************************************************************** class MediumAllPadding extends Padding { const MediumAllPadding({Key? key, required Widget child}) : super( key: key, padding: const EdgeInsets.all(PADDING_MEDIUM), child: child); } ///Other classes here, this line is not part of the real generated code '''); });

Slide 34

Slide 34 text

Nice helpful tips IDE code completion will be able to: - suggest you all the available Padding classes - transforms TLMP into TopLeftMediumPadding

Slide 35

Slide 35 text

Can you generate whatever you want?

Slide 36

Slide 36 text

Yes! Don't limit your imagination!

Slide 37

Slide 37 text

Thank you!

Slide 38

Slide 38 text

Drop a line to [email protected] Get in touch with me https://www.emanuelepapa.dev We are hiring @ ZestOne