Extensions and principles for modern Dart development.

Overview

neodart

Neo-Dart, or "new" Dart, is a series of recommended packages and principles that break out of classic conventions ("we've always done it that way") and have a bias towards change versus stability for stability sake.

TIP: Importantly, nobody should feel forced to use neo-Dart conventions.

We've contributed these to help others understand some of the trade-offs involved in building Dart/Flutter apps and libraries, but you can feel free to have your own opinion.

Additionally, some or all of these guidelines may not apply to a short-lived prototype, weekend warrior project, or something that just doesn't need to be particularly robust or contributed to by others.

Usage

Neo-Dart is a convention, and you can "use" it just by reading (and optionally, contributing) to this repository.

However, if you'd like to use a very opiniated set of lints that represent the conventions being used here:

# Add neodart as a development dependency.
dart pub add --dev neodart
# analysis_options.yaml
include: package:neodart/neodart.yaml

These rules contain include: package:lints/core.yaml, and then include additional stricter lints on top of them, always with a reason/rationale.

Contributing

To contribute, simply open an issue (major discussion) or a pull request (minor or no discussion needed, such as a spelling fix). There is no guarantee your request will be fulfilled, but changes to the guidelines are welcome in general.

As of 2022-07-05, github.com/matanlurey is the only decision maker, with hopes to expand.

Requirements

  1. Your request must have a well-documented reason beyond personal preference.
  2. Your request must be communictable in written English.
  3. Requests of the form "but X language/framework/library ..." are not enough.

Principles

The following are principles of neo-Dart packages and code:

  • Do not use: Should never occur in any code that is non-experimental.
  • Avoid: Should rarely occur in non-experimental code; is well documented.
  • Consider: Should be considered when appropriate.
  • Prefer: Should be the default unless well-documented otherwise.

Do not use dart:mirrors

Enforced: None. Ideally there would be a lint, so TBD.

Runtime reflection was never truly fully baked within Dart 1.x, and in modern Dart (2.x) it is relegated as third-class citizen, no longer supported in either Dart AOT (i.e. most Flutter binaries) or any Dart for the Web program.

In addition, even for command-line Dart JIT programs, mirrors is intentionally no longer being supported or updated to keep up with new language paradigms or primitives.

As a result do not use dart:mirrors, at all, even for testing. Runtime reflection often makes it difficult for new users and maintainers of a package and becomes overly clever. There is always a better way.

Do not use or override noSuchMethod

Enforced: None. Ideally there would be a lint, so TBD.

Somewhat similar to dart:mirrors, is noSuchMethod, a user-implementable "catch-all" for an unimplemented member on a Dart class. In modern Dart, it is mostly used for either stubbing (this class is not yet or intentionally not implemented) or mocking (i.e. with something like package:mockito).

Like mirrors, noSuchMethod has largely not caught up in the language and has both performance (runtime and code size) costs that aren't apparent to most developers.

There is almost always a better way than overriding noSuchMethod.

Do not make dynamic calls

Enforced: (via optional lint) avoid_dynamic_calls .

In Dart 1.x, every call was a dynamic call, and type signatures were only used for (quite limited) static analysis. Starting in Dart 2.x, most calls are static, and dynamic calls are limited only to objects (implicitly or explicitly) typed dynamic.

Dynamic calls are completely avoidable technical debt, disabling any sort of useful static analysis, making compilers work harder (and in most cases generate slower and less compact code), and are increasingly by accident.

Either use:

  • Object? with type checks (i.e. is) and casting.
  • Type inference, including with method generics.
  • Strongly typed custom objects instead of relying on foo.someMethod().

NOTE: Calls to type Function or Function.apply are also dynamic calls.

Do not use mocks

Enforced: (partially via lint) avoid_implementing_value_types , but requires more; TBD.

Mocking, or creating short-lived proxy objects that pretend to implement a type can be useful when testing certain contracts between related objects; for example, you may want to ensure that pet.eat(bowl) only calls the bowl.grab(...) method once with specific arguments (the pet's mouth size?).

However, mocking is generally extremely over-used. Typical apps and libraries usually have no need for mocking, and would be better served either by using the real object (especially for value-type immutable objects), and/or using or contributing to fakes - intentional sub-types intended for testing.

Avoid code generation

Enforced: None; not a hard rule and easy to catch in code review.

As a follow-up to not using dart:mirrors, also do not use code generation. Code generation can be a powerful tool, but is almost always overused and is not well supported in the Dart ecosystem (as of 2022-07-05).

Why to avoid code generation:

  • There is not strong support of tooling necessary; the build ecosystem is considered "best effort" and is not widely used by the Dart team itself; tools such as the analyzer do not understand the concept of code generation.

  • Code generation often produces patterns that would never be permitted within a codebase, but are otherwise hidden or obscured behind "generated code". Just because it's generated doesn't mean the code shouldn't be well understood and of good quality.

  • Code generation is too slow to be part of most serious developer cycles; often taking much longer than running Dart code otherwise, particularly within the VM.

However, this rule is avoid, not do not use; targeted use of code generation (i.e. to generate API clients or for JSON serialization) is permitted, but should be used judiciously; try developing your feature or library without code generation and narrow down the patterns before deciding on code generation.

NOTE: There is some promising upcoming language work in the form of macros that might allow this rule to be softened in the future, but even with macros you'll want to be careful around overuse.

Avoid heavy-weight dependencies when developing a library

Enforced: None; not a hard rule and easy to catch in code review.

Dependencies are not evil, they are what lets you create any non-trivial app without spending time re-inventing the wheel (particularly important when it comes to things like security, privacy, performance, or low-level computing).

However, be judicious when adding non-development dependencies to a library package, as each dependency you add will make it harder and harder for users of your library to upgrade and maintain over time. You should even consider using show when importing symbols from other packages into your library:

import 'package:other/other.dart' show ExplicitThingIAmUsing;

For example, when creating a hypothetical number_parser class, it might be tempting to use existing libraries for number formatting, string parsing, or anything in-between. However, ask yourself if you truly need many of the exact features in your dependencies, or if you could get away with a smaller (private) bespoke implementation.

TL;DR: In general, try to abide by the YAGNI policy.

Avoid creating classes that can or should be extended or implemented

Enforced: Partially through the analyzer (@sealed and factory).

This is shamelessly copied from Effective Java (Item 17):

Design and document for inheritance or else prohibit it.

In Dart, there are limited mechanisms for accomplishing this outside of documentation (i.e. /// Do not extend this class), but some suggestions are:

  • Use @sealed from package:meta; prohibits most kinds of inheritance:

    import 'package:meta/meta.dart';
    
    @sealed
    class Animal {
      final String name;
    
      Animal(this.name);
    }
  • Ensure all your public constructors are factory constructors:

    class Animal {
      final String name;
    
      // Factory constructors cannot be called/used by super-classes.
      factory Animal(String name) = Animal._;
    
      // Having a private constructor prevents `extends` and `with` (mixins).
      Animal._(this.name);
    }

Avoid mutable objects in public APIs

Enforced: None. Ideally there would be a lint, so TBD.

It's not desirable to completely avoid mutable state and APIs; at the end of the day all immutability is an abstraction.

However, for public APIs, design for immutability unless mutability is required (i.e. for performance or correctness reasons). Avoid mutating objects that provided to you (unless explicitly documenting you will mutate them):

/// Removes elements from [names] that match our explicit name filter.
void removeExplicit(Set<String> names) {
  // OK: Documented in the public API that "names" will be mutated.
}
You might also like...

Functional extensions to Dart collections.

collection_ext A set of extension methods for Dart collections, designed for the purpose of making it easier to write concise, functional-programming-

Nov 21, 2022

Enum extendable - Dart code generator. Generates enum extensions code.

Generates code for the extension on an enum. Overview Being able to add fields and methods to an enum. Let's say we have the following enum: enum Math

Jan 10, 2022

Package provides light widgets [for Linkify, Clean] and extensions for strings that contain bad words/URLs/links/emails/phone numbers

Package provides light widgets [for Linkify, Clean] and extensions for strings that contain bad words/URLs/links/emails/phone numbers

Package provides light widgets [for Linkify, Clean] and extensions for strings that contain bad words/URLs/links/emails/phone numbers

Oct 2, 2022

Awesome Flutter extensions to remove boilerplate

Awesome Flutter Extensions Awesome flutter extensions to remove boilerplate Installation Install using the Terminal: flutter pub add awesome_flutter_e

Aug 20, 2022

Various extensions on BuildContext to access inherited widget's state

context_extentions Getting inherited widget's state var themeData = context.theme; var scaffold = context.scaffold; var navigator = context.navi

Sep 23, 2021

Material io ext - A collection of extensions for creating widgets following material.io guidelines

material_io_ext It is a collection of extensions for creating widgets following

Jan 28, 2022

A simple PokeDex App with Modern UI created using Flutter and Dart and using API from PokeApi.

A simple PokeDex App with Modern UI created using Flutter and Dart and using API from PokeApi.

FlutterDex FlutterDex A simple PokeDex App created using Flutter and Dart and using API from PokeApi. UI Design inspired by : Home Screen Pokedex iOS

Jan 1, 2023

ANSI escape sequences and styling micro-library written in fluent/modern Dart.

ANSI escape sequences and styling micro-library written in fluent/modern Dart.

neoansi ANSI escape sequences and styling micro-library written in fluent/modern Dart. This library provides minimal ANSI escape sequences and helpers

Oct 31, 2022
Owner
Neo Dart
Neo Dart
Flutter-clean-architecture - A simple flutter project developed with TDD and using Clean Architecture principles.

Clean Architecture This is a study project to practice TDD and a good approach of Clean Architecture for flutter projects. It is based on Reso Coder s

Luiz Paulo Franz 8 Jul 21, 2022
This is a JazzCash UI clone ( Modern Wallet App in Pakistan), implementing modern app bar animmation. One can take a concept of making app bar with animation.

jazzcash_ui This is a JazzCash UI clone ( Modern Wallet App in Pakistan), implementing modern app bar animmation. One can take a concept of making app

null 9 Nov 27, 2022
Crypto Loss Gain Calculator App build with Flutter. It developed with DDD (Domain Driven Design) principles.

Crypto Loss Gain Calculator Crypto Loss Gain Calculator App build with Flutter. It developed with DDD (Domain Driven Design) principles. Domain-Driven

Selim Üstel 9 Dec 27, 2022
Multi Translator build with Flutter, It developed with DDD (Domain Driven Design) principles.

Multi Translator App An app utilizes to translate any text to multiple languages. Features Localization Multiple Translation Single Translation Deep L

Selim Üstel 7 Dec 27, 2022
Small calculator made to exemplify state management principles in Flutter using BLoC.

Oop Class Flutter Template Generated by the Very Good CLI ?? A Very Good Project created by Very Good CLI. Getting Started ?? This project contains 3

null 4 Dec 19, 2022
Pokedex-Flutter - Pokedex demonstrates modern Flutter development with GetX, Hive, Flow, Adaptive/Responsive Design

Pokedex-Flutter Pokedex demonstrates modern Flutter development with GetX, Hive,

Khoujani 3 Aug 17, 2022
[Flutter SDK V.2] - Youtube Video is a Flutter application built to demonstrate the use of Modern development tools with best practices implementation like Clean Architecture, Modularization, Dependency Injection, BLoC, etc.

[Flutter SDK V.2] - Youtube Video is a Flutter application built to demonstrate the use of Modern development tools with best practices implementation like Clean Architecture, Modularization, Dependency Injection, BLoC, etc.

R. Rifa Fauzi Komara 17 Jan 2, 2023
Raden Saleh 20 Aug 12, 2023
Raden Saleh 53 Jul 27, 2023
The Reactive Extensions for Dart

RxDart About RxDart extends the capabilities of Dart Streams and StreamControllers. Dart comes with a very decent Streams API out-of-the-box; rather t

ReactiveX 3.2k Dec 20, 2022