Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Supercharge Flutter Declarative UI with code generation

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/

A866828a311482aa142744ad177fc139?s=128

Emanuele Papa

December 02, 2021
Tweet

Other Decks in Programming

Transcript

  1. Supercharge Flutter Declarative UI with code generation Emanuele Papa droidcon

    London 2021
  2. Working at Zest One in Chiasso, Switzerland Who I am?

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

    SwiftUI Unfortunately, not perfect yet No more XML or UIKit
  4. Padding in Flutter Padding( padding: EdgeInsets.all(16.0), child: Text("Hello droidcon!") );

  5. 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
  6. Looking for improvements Someone else experienced the same struggle Some

    libraries are available on GitHub
  7. It's an improvement, but you still need to add the

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

    manually, it's specified in the class name MediumTopPadding(child: Text("Hello droidcon!"));
  9. How to create these widgets?

  10. Paddinger

  11. How to use Paddinger? dependencies: paddinger_annotations: [latestVersionHere] dev_dependencies: paddinger: [latestVersionHere]

    Add these dependencies to your pubspec.yaml
  12. 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;
  13. How to use Paddinger? Add material or cupertino import Add

    part directive // ignore: unused_import import 'package:flutter/material.dart'; part 'paddings.g.dart';
  14. Start the code generation Now run flutter pub run build_runner

    build --delete-conflicting- outputs
  15. 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
  16. 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); }
  17. 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
  18. Let's deep dive inside paddinger source code!

  19. Paddinger source code Monorepo with 2 projects: - paddinger -

    paddinger_annotations
  20. 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
  21. 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
  22. Paddinger source code builders: paddinger_generator: import: "package:paddinger/paddinger.dart" builder_factories: ["paddingerGenerator"] build_extensions:

    {".dart": [".paddinger.g.part"]} build.yaml
  23. Paddinger source code ///Paddinger generator used with [Builder] Builder paddingerGenerator(BuilderOptions

    options) => SharedPartBuilder([PaddingerGenerator()], 'paddinger_generator'); paddinger.dart
  24. 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
  25. 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)} '''; }
  26. 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); } '''; }
  27. 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); }
  28. 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(); }
  29. 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
  30. How can you test the generated code?

  31. 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>())); });
  32. 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"); });
  33. 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 '''); });
  34. Nice helpful tips IDE code completion will be able to:

    - suggest you all the available Padding classes - transforms TLMP into TopLeftMediumPadding
  35. Can you generate whatever you want?

  36. Yes! Don't limit your imagination!

  37. Thank you!

  38. Drop a line to info@zest.one Get in touch with me

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