patch 1.0.1 copy "patch: ^1.0.1" to clipboard
patch: ^1.0.1 copied to clipboard

A wrapper for copyWith arguments, so that a nullable field can be cleared as well as replaced.

patch #

A wrapper for copyWith arguments, so that a nullable field can be set to null or left unchanged.

In a typical copyWith, a nullable field cannot be assigned to null - it would read the same as "unchanged."

Getting started #

dart pub add patch

Or add it to your pubspec.yaml directly:

dependencies:
  patch: ^1.0.0

Requires Dart 3.7 or later.

Usage #

Declare each copyWith parameter as a Patch that defaults to Unchanged, and resolve it against the field's current value:

import 'package:patch/patch.dart';

class UserProfile {
  UserProfile(this.name, this.nickname);

  final String name;
  final String? nickname;
}

extension on UserProfile {
  UserProfile copyWith({
    Patch<String?> nickname = const Unchanged(),
  }) {
    return UserProfile(
      name,
      nickname.resolve(this.nickname),
    );
  }
}

All three outcomes are reachable:

final profile = UserProfile('Ada Lovelace', 'Ada');

profile.copyWith().nickname;                                   // 'Ada'
profile.copyWith(nickname: const Value('Countess')).nickname;  // 'Countess'
// Because nickname is nullable, we can also "Clear" it
profile.copyWith(nickname: const Clear()).nickname;            // null

For a runnable version, see example/patch_example.dart.

.resolve #

resolve is just a helper that picks the new value, when provided, or the current value otherwise.

final resolved = switch (nickname) {
  Value(value: final v) => v,
  Unchanged() => this.nickname,
};

The variants #

Variant The caller The field becomes
Value(v) passed a value v
Unchanged() passed nothing unchanged
Clear() asked to remove the value null

Clear #

Clear is just a special case of Value<Null> to more clearly indicate its intent. Attempting to assign Clear to a non-nullable value results in a compile-time error. You do not need to handle a Clear() in a switch for it to be exhaustive.

Non-nullable fields #

A non-nullable parameter uses the same API:

name.resolve(this.name)

Mentioned above, clearing a non-nullable field does not pass type-checking:

profile.copyWith(name: const Clear());
//                     ^ The argument type 'Clear' can't be assigned to the
//                       parameter type 'Patch<String>'.

Strong typing #

The type argument in a Patch is necessary. A bare Patch becomes a Patch<dynamic>, which accepts a Clear no matter what the field's type actually is. This opens the door for runtime errors. Enable strict-raw-types in your analysis_options.yaml to catch this.

analyzer:
  language:
    strict-raw-types: true

Why resolve is an extension #

Clear is a Value<Null>. If resolve were a real method on Patch<T>, then calling it on a Clear would type current as Null, and clearing a populated field would throw. Extensions are dispatched statically, so current gets its type from the call site instead.

The cost is that you have to call it on something typed Patch<T?>:

const Clear().resolve('Ada');            // won't compile; T infers as Null

final Patch<String?> patch = const Clear();
patch.resolve('Ada');                    // null

copyWith parameters are already Patch<T?>, so you rarely hit this.

Equality #

The variants are value types, so two separate Value instances with the same value are equal. All Unchanged are equal to each other, too. This allows Patches to compare and hash and even work as Map keys and Set members.

const Value('a') == const Value('a');                      // true
// equality is based on the _value_, not the type
const Value<Object>('a') == const Value('a');              // true
const Unchanged<String>() == const Unchanged<int>();       // true
const Unchanged<String?>() == const Value<String?>(null);  // false

Note: since Clear() is Value(null), the two are equal.

const Clear() == const Value<String?>(null); // true

Use is Clear when you need to tell an explicit clear apart from an incidental null - equality will not do it for you.

0
likes
160
points
146
downloads

Documentation

API reference

Publisher

verified publisherandyhorn.dev

Weekly Downloads

A wrapper for copyWith arguments, so that a nullable field can be cleared as well as replaced.

Repository (GitHub)
View/report issues

Topics

#copywith #immutability #data-classes

License

MIT (license)

Dependencies

meta

More

Packages that depend on patch