$30 off During Our Annual Pro Sale. View Details »

droidcon London 2021 - Supercharge Flutter Decl...

droidcon London 2021 - Supercharge Flutter Declarative UI with code generation

Slides used during my talk at droidcon London 2021 about how to improve your productivity in Flutter using a code generator to help you create Padding widgets based on your Desing System.

Talk video: https://www.droidcon.com/2021/11/22/supercharge-flutter-declarative-ui-with-code-generation/

Emanuele Papa

December 02, 2021
Tweet

More Decks by Emanuele Papa

Other Decks in Programming

Transcript

  1. Working at Zest One in Chiasso, Switzerland Who I am?

    Emanuele Papa, Android Developer Find me at www.emanuelepapa.dev
  2. Declarative UI Imperative UI -> Declarative UI Jetpack Compose and

    SwiftUI Unfortunately, not perfect yet No more XML or UIKit
  3. 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
  4. It's an improvement, but you still need to add the

    Padding value manually The missing thing PaddingTop(16.0, child: Text("Hello droidcon!"));
  5. Design System Padding No need to add the Padding value

    manually, it's specified in the class name MediumTopPadding(child: Text("Hello droidcon!"));
  6. 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;
  7. How to use Paddinger? Add material or cupertino import Add

    part directive // ignore: unused_import import 'package:flutter/material.dart'; part 'paddings.g.dart';
  8. 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
  9. 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); }
  10. 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
  11. 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
  12. 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
  13. Paddinger source code ///Paddinger generator used with [Builder] Builder paddingerGenerator(BuilderOptions

    options) => SharedPartBuilder([PaddingerGenerator()], 'paddinger_generator'); paddinger.dart
  14. Paddinger source code class PaddingerGenerator extends GeneratorForAnnotation<Paddinger> { @override FutureOr<String>

    generateForAnnotatedElement( Element element, ConstantReader annotation, BuildStep buildStep) { final constantName = element.name!; validatePaddingConstantName(constantName); final generatedName = toNameForGeneration(constantName); return generatePaddingWidgets(generatedName, constantName); } } paddinger_generator.dart
  15. 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)} '''; }
  16. 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); } '''; }
  17. 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); }
  18. 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(); }
  19. 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
  20. 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<Exception>())); });
  21. 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"); });
  22. 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 '''); });
  23. Nice helpful tips IDE code completion will be able to:

    - suggest you all the available Padding classes - transforms TLMP into TopLeftMediumPadding
  24. Drop a line to [email protected] Get in touch with me

    https://www.emanuelepapa.dev We are hiring @ ZestOne